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 CLineGraphDraw extends CGraphDraw { 23 const GRAPH_WIDTH_MIN = 20; 24 const GRAPH_HEIGHT_MIN = 20; 25 const legendOffsetY = 90; 26 27 public function __construct($type = GRAPH_TYPE_NORMAL) { 28 parent::__construct($type); 29 $this->yaxismin = null; 30 $this->yaxismax = null; 31 $this->triggers = []; 32 $this->ymin_type = GRAPH_YAXIS_TYPE_CALCULATED; 33 $this->ymax_type = GRAPH_YAXIS_TYPE_CALCULATED; 34 $this->yaxis = [ 35 GRAPH_YAXIS_SIDE_LEFT => false, 36 GRAPH_YAXIS_SIDE_RIGHT => false 37 ]; 38 $this->skipLeftScale = 0; // in case if left axis should be drawn but doesn't contain any data 39 $this->skipRightScale = 0; // in case if right axis should be drawn but doesn't contain any data 40 $this->ymin_itemid = 0; 41 $this->ymax_itemid = 0; 42 $this->percentile = [ 43 GRAPH_YAXIS_SIDE_LEFT => [ 44 'percent' => 0, // draw percentage line 45 'value' => 0 // calculated percentage value left y axis 46 ], 47 GRAPH_YAXIS_SIDE_RIGHT => [ 48 'percent' => 0, // draw percentage line 49 'value' => 0 // calculated percentage value right y axis 50 ] 51 ]; 52 $this->outer = false; 53 $this->m_showWorkPeriod = 1; 54 $this->m_showTriggers = 1; 55 $this->zero = []; 56 $this->graphOrientation = [ 57 GRAPH_YAXIS_SIDE_LEFT => '', 58 GRAPH_YAXIS_SIDE_RIGHT => '' 59 ]; 60 $this->gridLinesCount = []; // How many grids to draw 61 $this->gridStep = []; // grid step 62 $this->gridPixels = 30; // optimal grid size 63 $this->gridPixelsVert = 40; 64 $this->axis_valuetype = []; // overal items type (int/float) 65 $this->drawItemsLegend = false; // draw items legend 66 $this->drawExLegend = false; // draw percentile and triggers legend 67 } 68 69 /********************************************************************************************************/ 70 // PRE CONFIG: ADD / SET / APPLY 71 /********************************************************************************************************/ 72 public function showWorkPeriod($value) { 73 $this->m_showWorkPeriod = ($value == 1) ? 1 : 0; 74 } 75 76 public function showTriggers($value) { 77 $this->m_showTriggers = ($value == 1) ? 1 : 0; 78 } 79 80 /** 81 * Add single item object to graph. If invalid 'delay' interval passed method will interrupt current request with 82 * error message. 83 * 84 * @param array $item Array of graph item properties. 85 * @param string $item['itemid'] Item id. 86 * @param string $item['type'] Item type. 87 * @param string $item['name'] Item host display name. 88 * @param string $item['hostname'] Item hostname. 89 * @param string $item['key_'] Item key_ field value. 90 * @param string $item['value_type'] Item value type. 91 * @param string $item['history'] Item history field value. 92 * @param string $item['trends'] Item trends field value. 93 * @param string $item['delay'] Item delay. 94 * @param string $item['master_itemid'] Master item id for item of type ITEM_TYPE_DEPENDENT. 95 * @param string $item['units'] Item units value. 96 * @param string $item['hostid'] Item host id. 97 * @param string $item['hostname'] Item host name. 98 * @param string $item['color'] Item presentation color. 99 * @param int $item['drawtype'] Item presentation draw type, could be one of GRAPH_ITEM_DRAWTYPE_* constants. 100 * @param int $item['yaxisside'] Item axis side, could be one of GRAPH_YAXIS_SIDE_* constants. 101 * @param int $item['calc_fnc'] Item calculation function, could be one of CALC_FNC_* constants. 102 * @param int $item['calc_type'] Item graph presentation calculation type, GRAPH_ITEM_SIMPLE or GRAPH_ITEM_SUM. 103 * 104 */ 105 public function addItem(array $graph_item) { 106 if ($this->type == GRAPH_TYPE_STACKED) { 107 $graph_item['drawtype'] = GRAPH_ITEM_DRAWTYPE_FILLED_REGION; 108 } 109 $update_interval_parser = new CUpdateIntervalParser(['usermacros' => true]); 110 111 if ($update_interval_parser->parse($graph_item['delay']) != CParser::PARSE_SUCCESS) { 112 show_error_message(_s('Incorrect value for field "%1$s": %2$s.', 'delay', _('invalid delay'))); 113 exit; 114 } 115 116 // Set graph item safe default values. 117 $graph_item += [ 118 'color' => 'Dark Green', 119 'drawtype' => GRAPH_ITEM_DRAWTYPE_LINE, 120 'yaxisside' => GRAPH_YAXIS_SIDE_DEFAULT, 121 'calc_fnc' => CALC_FNC_AVG, 122 'calc_type' => GRAPH_ITEM_SIMPLE 123 ]; 124 $this->items[$this->num] = $graph_item; 125 126 $this->yaxis[$graph_item['yaxisside']] = true; 127 128 $this->num++; 129 } 130 131 public function setGraphOrientation($value, $yaxisside) { 132 if ($value < 0) { 133 $this->graphOrientation[$yaxisside] = '-'; 134 } 135 elseif (zbx_empty($this->graphOrientation[$yaxisside]) && $value > 0) { 136 $this->graphOrientation[$yaxisside] = '+'; 137 } 138 return $this->graphOrientation[$yaxisside]; 139 } 140 141 public function setYMinAxisType($yaxistype) { 142 $this->ymin_type = $yaxistype; 143 } 144 145 public function setYMaxAxisType($yaxistype) { 146 $this->ymax_type = $yaxistype; 147 } 148 149 public function setYAxisMin($yaxismin) { 150 $this->yaxismin = $yaxismin; 151 } 152 153 public function setYAxisMax($yaxismax) { 154 $this->yaxismax = $yaxismax; 155 } 156 157 public function setYMinItemId($itemid) { 158 $this->ymin_itemid = $itemid; 159 } 160 161 public function setYMaxItemId($itemid) { 162 $this->ymax_itemid = $itemid; 163 } 164 165 public function setLeftPercentage($percentile) { 166 $this->percentile[GRAPH_YAXIS_SIDE_LEFT]['percent'] = $percentile; 167 } 168 169 public function setRightPercentage($percentile) { 170 $this->percentile[GRAPH_YAXIS_SIDE_RIGHT]['percent'] = $percentile; 171 } 172 173 public function setOuter($outer) { 174 $this->outer = $outer; 175 } 176 177 protected function selectData() { 178 $this->data = []; 179 $now = time(); 180 181 if (!isset($this->stime)) { 182 $this->stime = $now - $this->period; 183 } 184 185 $this->diffTZ = (date('Z', $this->stime) - date('Z', $this->stime + $this->period)); 186 $this->from_time = $this->stime; // + timeZone offset 187 $this->to_time = $this->stime + $this->period; // + timeZone offset 188 189 $p = $this->to_time - $this->from_time; // graph size in time 190 $x = $this->sizeX; // graph size in px 191 192 $this->itemsHost = null; 193 194 $config = select_config(); 195 $items = []; 196 197 for ($i = 0; $i < $this->num; $i++) { 198 $item = $this->items[$i]; 199 200 if ($this->itemsHost === null) { 201 $this->itemsHost = $item['hostid']; 202 } 203 elseif ($this->itemsHost != $item['hostid']) { 204 $this->itemsHost = false; 205 } 206 207 if (!isset($this->axis_valuetype[$item['yaxisside']])) { 208 $this->axis_valuetype[$item['yaxisside']] = $item['value_type']; 209 } 210 elseif ($this->axis_valuetype[$item['yaxisside']] != $item['value_type']) { 211 $this->axis_valuetype[$item['yaxisside']] = ITEM_VALUE_TYPE_FLOAT; 212 } 213 214 $type = $item['calc_type']; 215 $to_resolve = []; 216 217 // Override item history setting with housekeeping settings, if they are enabled in config. 218 if ($config['hk_history_global']) { 219 $item['history'] = timeUnitToSeconds($config['hk_history']); 220 } 221 else { 222 $to_resolve[] = 'history'; 223 } 224 225 if ($config['hk_trends_global']) { 226 $item['trends'] = timeUnitToSeconds($config['hk_trends']); 227 } 228 else { 229 $to_resolve[] = 'trends'; 230 } 231 232 // Otherwise, resolve user macro and parse the string. If successful, convert to seconds. 233 if ($to_resolve) { 234 $item = CMacrosResolverHelper::resolveTimeUnitMacros([$item], $to_resolve)[0]; 235 236 $simple_interval_parser = new CSimpleIntervalParser(); 237 238 if (!$config['hk_history_global']) { 239 if ($simple_interval_parser->parse($item['history']) != CParser::PARSE_SUCCESS) { 240 show_error_message(_s('Incorrect value for field "%1$s": %2$s.', 'history', 241 _('invalid history storage period') 242 )); 243 exit; 244 } 245 $item['history'] = timeUnitToSeconds($item['history']); 246 } 247 248 if (!$config['hk_trends_global']) { 249 if ($simple_interval_parser->parse($item['trends']) != CParser::PARSE_SUCCESS) { 250 show_error_message(_s('Incorrect value for field "%1$s": %2$s.', 'trends', 251 _('invalid trend storage period') 252 )); 253 exit; 254 } 255 $item['trends'] = timeUnitToSeconds($item['trends']); 256 } 257 } 258 259 $item['source'] = ($item['trends'] == 0 || ($item['history'] > time() - ($this->from_time + $this->period / 2) 260 && $this->period / $this->sizeX <= ZBX_MAX_TREND_DIFF / ZBX_GRAPH_MAX_SKIP_CELL)) 261 ? 'history' : 'trends'; 262 263 $items[] = $item; 264 } 265 266 $results = Manager::History()->getGraphAggregation($items, $this->from_time, $this->to_time, $x); 267 268 foreach ($items as $item) { 269 if (!array_key_exists($item['itemid'], $this->data)) { 270 $this->data[$item['itemid']] = []; 271 } 272 273 if (!array_key_exists($type, $this->data[$item['itemid']])) { 274 $this->data[$item['itemid']][$type] = []; 275 } 276 277 $curr_data = &$this->data[$item['itemid']][$type]; 278 279 $curr_data['count'] = null; 280 $curr_data['min'] = null; 281 $curr_data['max'] = null; 282 $curr_data['avg'] = null; 283 $curr_data['clock'] = null; 284 285 if (array_key_exists($item['itemid'], $results)) { 286 $result = $results[$item['itemid']]; 287 $this->dataFrom = $result['source']; 288 289 foreach ($result['data'] as $row) { 290 $idx = $row['i'] - 1; 291 if ($idx < 0) { 292 continue; 293 } 294 295 /* -------------------------------------------------- 296 We are taking graph on 1px more than we need, 297 and here we are skipping first px, because of MOD (in SELECT), 298 it combines prelast point (it would be last point if not that 1px in beginning) 299 and first point, but we still losing prelast point :( 300 but now we've got the first point. 301 --------------------------------------------------*/ 302 $curr_data['count'][$idx] = $row['count']; 303 $curr_data['min'][$idx] = $row['min']; 304 $curr_data['max'][$idx] = $row['max']; 305 $curr_data['avg'][$idx] = $row['avg']; 306 $curr_data['clock'][$idx] = $row['clock']; 307 $curr_data['shift_min'][$idx] = 0; 308 $curr_data['shift_max'][$idx] = 0; 309 $curr_data['shift_avg'][$idx] = 0; 310 } 311 312 unset($result); 313 } 314 else { 315 $this->dataFrom = $item['source']; 316 } 317 318 $loc_min = is_array($curr_data['min']) ? min($curr_data['min']) : null; 319 $this->setGraphOrientation($loc_min, $item['yaxisside']); 320 321 $curr_data['avg_orig'] = is_array($curr_data['avg']) ? zbx_avg($curr_data['avg']) : null; 322 323 // calculate missed points 324 $first_idx = 0; 325 326 /* 327 first_idx - last existing point 328 ci - current index 329 cj - count of missed in one go 330 dx - offset to first value (count to last existing point) 331 */ 332 for ($ci = 0, $cj = 0; $ci < $this->sizeX; $ci++) { 333 if (!isset($curr_data['count'][$ci]) || ($curr_data['count'][$ci] == 0)) { 334 $curr_data['count'][$ci] = 0; 335 $curr_data['shift_min'][$ci] = 0; 336 $curr_data['shift_max'][$ci] = 0; 337 $curr_data['shift_avg'][$ci] = 0; 338 $cj++; 339 continue; 340 } 341 342 if ($cj == 0) { 343 continue; 344 } 345 346 $dx = $cj + 1; 347 $first_idx = $ci - $dx; 348 349 if ($first_idx < 0) { 350 $first_idx = $ci; // if no data from start of graph get current data as first data 351 } 352 353 for(; $cj > 0; $cj--) { 354 if ($dx < ($this->sizeX / 20) && $this->type == GRAPH_TYPE_STACKED) { 355 $curr_data['count'][$ci - ($dx - $cj)] = 1; 356 } 357 358 foreach (['clock', 'min', 'max', 'avg'] as $var_name) { 359 $var = &$curr_data[$var_name]; 360 361 if ($first_idx == $ci && $var_name == 'clock') { 362 $var[$ci - ($dx - $cj)] = $var[$first_idx] - (($p / $this->sizeX) * ($dx - $cj)); 363 continue; 364 } 365 366 $dy = $var[$ci] - $var[$first_idx]; 367 $var[$ci - ($dx - $cj)] = bcadd($var[$first_idx] , bcdiv(($cj * $dy) , $dx)); 368 } 369 } 370 } 371 372 if ($cj > 0 && $ci > $cj) { 373 $dx = $cj + 1; 374 $first_idx = $ci - $dx; 375 376 for(;$cj > 0; $cj--) { 377 foreach (['clock', 'min', 'max', 'avg'] as $var_name) { 378 $var = &$curr_data[$var_name]; 379 380 if ($var_name == 'clock') { 381 $var[$first_idx + ($dx - $cj)] = $var[$first_idx] + (($p / $this->sizeX) * ($dx - $cj)); 382 continue; 383 } 384 $var[$first_idx + ($dx - $cj)] = $var[$first_idx]; 385 } 386 } 387 } 388 } 389 390 unset($items); 391 unset($results); 392 393 // calculate shift for stacked graphs 394 if ($this->type == GRAPH_TYPE_STACKED) { 395 for ($i = 1; $i < $this->num; $i++) { 396 $item1 = $this->items[$i]; 397 398 $curr_data = &$this->data[$item1['itemid']][$item1['calc_type']]; 399 400 if (!isset($curr_data)) { 401 continue; 402 } 403 404 for ($j = $i - 1; $j >= 0; $j--) { 405 $item2 = $this->items[$j]; 406 407 if ($item2['yaxisside'] != $item1['yaxisside']) { 408 continue; 409 } 410 411 $prev_data = &$this->data[$item2['itemid']][$item2['calc_type']]; 412 413 if (!isset($prev_data)) { 414 continue; 415 } 416 417 for ($ci = 0; $ci < $this->sizeX; $ci++) { 418 foreach (['min', 'max', 'avg'] as $var_name) { 419 $shift_var_name = 'shift_'.$var_name; 420 $curr_shift = &$curr_data[$shift_var_name]; 421 $prev_shift = &$prev_data[$shift_var_name]; 422 $prev_var = &$prev_data[$var_name]; 423 424 $prev_var_ci = ($prev_var === null) ? 0 : $prev_var[$ci]; 425 $prev_shift_ci = ($prev_shift === null) ? 0 : $prev_shift[$ci]; 426 $curr_shift[$ci] = $prev_var_ci + $prev_shift_ci; 427 } 428 } 429 break; 430 } 431 } 432 } 433 } 434 435 protected function selectTriggers() { 436 $this->triggers = []; 437 if ($this->m_showTriggers != 1) { 438 return; 439 } 440 441 $max = 3; 442 $cnt = 0; 443 444 foreach ($this->items as $item) { 445 $db_triggers = DBselect( 446 'SELECT DISTINCT h.host,tr.description,tr.triggerid,tr.expression,tr.priority,tr.value'. 447 ' FROM triggers tr,functions f,items i,hosts h'. 448 ' WHERE tr.triggerid=f.triggerid'. 449 " AND f.name IN ('last','min','avg','max')". 450 ' AND tr.status='.TRIGGER_STATUS_ENABLED. 451 ' AND i.itemid=f.itemid'. 452 ' AND h.hostid=i.hostid'. 453 ' AND f.itemid='.zbx_dbstr($item['itemid']). 454 ' ORDER BY tr.priority' 455 ); 456 while (($trigger = DBfetch($db_triggers)) && $cnt < $max) { 457 $db_fnc_cnt = DBselect( 458 'SELECT COUNT(*) AS cnt'. 459 ' FROM functions f'. 460 ' WHERE f.triggerid='.zbx_dbstr($trigger['triggerid']) 461 ); 462 $fnc_cnt = DBfetch($db_fnc_cnt); 463 464 if ($fnc_cnt['cnt'] != 1) { 465 continue; 466 } 467 468 $trigger['expression'] = CMacrosResolverHelper::resolveTriggerExpressionUserMacro($trigger); 469 470 if (!preg_match( 471 '/^\{([0-9]+)\}\s*?([<>=]|[<>][=])\s*?([\-0-9\.]+)(['.ZBX_BYTE_SUFFIXES.ZBX_TIME_SUFFIXES.']?)$/', 472 $trigger['expression'], $arr)) { 473 continue; 474 } 475 476 $constant = $arr[3].$arr[4]; 477 478 $this->triggers[] = [ 479 'yaxisside' => $item['yaxisside'], 480 'val' => convert($constant), 481 'color' => getSeverityColor($trigger['priority']), 482 'description' => _('Trigger').NAME_DELIMITER.CMacrosResolverHelper::resolveTriggerName($trigger), 483 'constant' => '['.$arr[2].' '.$constant.']' 484 ]; 485 ++$cnt; 486 } 487 } 488 } 489 490 /********************************************************************************************************/ 491 // CALCULATIONS 492 /********************************************************************************************************/ 493 // calculates percentages for left & right Y axis 494 protected function calcPercentile() { 495 if ($this->type != GRAPH_TYPE_NORMAL) { 496 return ; 497 } 498 499 $values = [ 500 GRAPH_YAXIS_SIDE_LEFT => [], 501 GRAPH_YAXIS_SIDE_RIGHT=> [] 502 ]; 503 504 $maxX = $this->sizeX; 505 506 // for each metric 507 for ($item = 0; $item < $this->num; $item++) { 508 $data = &$this->data[$this->items[$item]['itemid']][$this->items[$item]['calc_type']]; 509 510 if (!isset($data)) { 511 continue; 512 } 513 514 // for each X 515 for ($i = 0; $i < $maxX; $i++) { // new point 516 if ($data['count'][$i] == 0) { 517 continue; 518 } 519 520 $min = $data['min'][$i]; 521 $max = $data['max'][$i]; 522 $avg = $data['avg'][$i]; 523 524 switch ($this->items[$item]['calc_fnc']) { 525 case CALC_FNC_MAX: 526 $value = $max; 527 break; 528 case CALC_FNC_MIN: 529 $value = $min; 530 break; 531 case CALC_FNC_ALL: 532 case CALC_FNC_AVG: 533 default: 534 $value = $avg; 535 } 536 537 $values[$this->items[$item]['yaxisside']][] = $value; 538 } 539 } 540 541 foreach ($this->percentile as $side => $percentile) { 542 if ($percentile['percent'] > 0 && $values[$side]) { 543 sort($values[$side]); 544 // Using "Nearest Rank" method: http://en.wikipedia.org/wiki/Percentile#Definition_of_the_Nearest_Rank_method 545 $percent = (int) ceil($percentile['percent'] / 100 * count($values[$side])); 546 $this->percentile[$side]['value'] = $values[$side][$percent - 1]; 547 } 548 } 549 } 550 551 // calculation of minimum Y axis 552 protected function calculateMinY($side) { 553 if ($this->ymin_type == GRAPH_YAXIS_TYPE_FIXED) { 554 return $this->yaxismin; 555 } 556 557 if ($this->ymin_type == GRAPH_YAXIS_TYPE_ITEM_VALUE && $this->ymin_itemid != 0) { 558 $item = get_item_by_itemid($this->ymin_itemid); 559 if ($item) { 560 $history = Manager::History()->getLastValues([$item]); 561 if (isset($history[$item['itemid']])) { 562 return $history[$item['itemid']][0]['value']; 563 } 564 } 565 } 566 567 $minY = null; 568 for ($i = 0; $i < $this->num; $i++) { 569 if ($this->items[$i]['yaxisside'] != $side) { 570 continue; 571 } 572 573 if (!isset($this->data[$this->items[$i]['itemid']][GRAPH_ITEM_SIMPLE])) { 574 continue; 575 } 576 577 $data = &$this->data[$this->items[$i]['itemid']][GRAPH_ITEM_SIMPLE]; 578 579 if (!isset($data)) { 580 continue; 581 } 582 583 $calc_fnc = $this->items[$i]['calc_fnc']; 584 585 switch ($calc_fnc) { 586 case CALC_FNC_ALL: 587 case CALC_FNC_MIN: 588 $val = $data['min']; 589 $shift_val = $data['shift_min']; 590 break; 591 case CALC_FNC_MAX: 592 $val = $data['max']; 593 $shift_val = $data['shift_max']; 594 break; 595 case CALC_FNC_AVG: 596 default: 597 $val = $data['avg']; 598 $shift_val = $data['shift_avg']; 599 } 600 601 if (!isset($val)) { 602 continue; 603 } 604 605 if ($this->type == GRAPH_TYPE_STACKED) { 606 $min_val_shift = min(count($val), count($shift_val)); 607 for ($ci = 0; $ci < $min_val_shift; $ci++) { 608 if ($shift_val[$ci] < 0) { 609 $val[$ci] += bcadd($shift_val[$ci], $val[$ci]); 610 } 611 } 612 } 613 614 if (!isset($minY)) { 615 if (isset($val) && count($val) > 0) { 616 $minY = min($val); 617 } 618 } 619 else { 620 $minY = min($minY, min($val)); 621 } 622 } 623 624 return $minY; 625 } 626 627 // calculation of maximum Y of a side (left/right) 628 protected function calculateMaxY($side) { 629 if ($this->ymax_type == GRAPH_YAXIS_TYPE_FIXED) { 630 return $this->yaxismax; 631 } 632 633 if ($this->ymax_type == GRAPH_YAXIS_TYPE_ITEM_VALUE && $this->ymax_itemid != 0) { 634 $item = get_item_by_itemid($this->ymax_itemid); 635 if ($item) { 636 $history = Manager::History()->getLastValues([$item]); 637 if (isset($history[$item['itemid']])) { 638 return $history[$item['itemid']][0]['value']; 639 } 640 } 641 } 642 643 $maxY = null; 644 for ($i = 0; $i < $this->num; $i++) { 645 if ($this->items[$i]['yaxisside'] != $side) { 646 continue; 647 } 648 649 if (!isset($this->data[$this->items[$i]['itemid']][GRAPH_ITEM_SIMPLE])) { 650 continue; 651 } 652 653 $data = &$this->data[$this->items[$i]['itemid']][GRAPH_ITEM_SIMPLE]; 654 655 if (!isset($data)) { 656 continue; 657 } 658 659 $calc_fnc = $this->items[$i]['calc_fnc']; 660 661 switch ($calc_fnc) { 662 case CALC_FNC_ALL: 663 case CALC_FNC_MAX: 664 $val = $data['max']; 665 $shift_val = $data['shift_max']; 666 break; 667 case CALC_FNC_MIN: 668 $val = $data['min']; 669 $shift_val = $data['shift_min']; 670 break; 671 case CALC_FNC_AVG: 672 default: 673 $val = $data['avg']; 674 $shift_val = $data['shift_avg']; 675 } 676 677 if (!isset($val)) { 678 continue; 679 } 680 681 for ($ci = 0; $ci < min(count($val), count($shift_val)); $ci++) { 682 if ($data['count'][$ci] == 0) { 683 continue; 684 } 685 686 $val[$ci] = bcadd($shift_val[$ci], $val[$ci]); 687 } 688 689 if (!isset($maxY)) { 690 if (isset($val) && count($val) > 0) { 691 $maxY = max($val); 692 } 693 } 694 else { 695 $maxY = max($maxY, max($val)); 696 } 697 } 698 699 return $maxY; 700 } 701 702 /** 703 * Check if Y axis min value is larger than Y axis max value. Show error instead of graph if true. 704 * 705 * @param float $min Y axis min value 706 * @param float $max Y axis max value 707 */ 708 protected function validateMinMax($min, $max) { 709 if (bccomp($min, $max) == 0 || bccomp($min, $max) == 1) { 710 show_error_message(_('Y axis MAX value must be greater than Y axis MIN value.')); 711 exit; 712 } 713 } 714 715 protected function calcZero() { 716 if (isset($this->axis_valuetype[GRAPH_YAXIS_SIDE_RIGHT])) { 717 $sides[] = GRAPH_YAXIS_SIDE_RIGHT; 718 } 719 720 if (isset($this->axis_valuetype[GRAPH_YAXIS_SIDE_LEFT]) || !isset($sides)) { 721 $sides[] = GRAPH_YAXIS_SIDE_LEFT; 722 } 723 724 foreach ($sides as $num => $side) { 725 $this->unit2px[$side] = ($this->m_maxY[$side] - $this->m_minY[$side]) / $this->sizeY; 726 if ($this->unit2px[$side] == 0) { 727 $this->unit2px[$side] = 1; 728 } 729 730 if ($this->m_minY[$side] > 0) { 731 $this->zero[$side] = $this->sizeY + $this->shiftY; 732 if (bccomp($this->m_minY[$side], $this->m_maxY[$side]) == 1) { 733 $this->oxy[$side] = $this->m_maxY[$side]; 734 } 735 else { 736 $this->oxy[$side] = $this->m_minY[$side]; 737 } 738 } 739 elseif ($this->m_maxY[$side] < 0) { 740 $this->zero[$side] = $this->shiftY; 741 if (bccomp($this->m_minY[$side], $this->m_maxY[$side]) == 1) { 742 $this->oxy[$side] = $this->m_minY[$side]; 743 } 744 else { 745 $this->oxy[$side] = $this->m_maxY[$side]; 746 } 747 } 748 else { 749 $this->zero[$side] = $this->sizeY + $this->shiftY - abs(bcdiv($this->m_minY[$side], 750 $this->unit2px[$side] 751 )); 752 $this->oxy[$side] = 0; 753 } 754 } 755 } 756 757 protected function calcMinMaxInterval() { 758 $intervals = []; 759 foreach ([1, 2, 3, 4] as $num) { 760 $dec = pow(0.1, $num); 761 foreach ([1, 2, 5] as $int) { 762 $intervals[] = bcmul($int, $dec); 763 } 764 } 765 766 // Check if items use B or Bps units. 767 $leftBase1024 = false; 768 $rightBase1024 = false; 769 770 for ($item = 0; $item < $this->num; $item++) { 771 if ($this->items[$item]['units'] == 'B' || $this->items[$item]['units'] == 'Bps') { 772 if ($this->items[$item]['yaxisside'] == GRAPH_YAXIS_SIDE_LEFT) { 773 $leftBase1024 = true; 774 } 775 else { 776 $rightBase1024 = true; 777 } 778 } 779 } 780 781 foreach ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18] as $num) { 782 $dec = bcpow(10, $num); 783 foreach ([1, 2, 5] as $int) { 784 $intervals[] = bcmul($int, $dec); 785 } 786 } 787 788 if (isset($this->axis_valuetype[GRAPH_YAXIS_SIDE_RIGHT])) { 789 $sides[] = GRAPH_YAXIS_SIDE_RIGHT; 790 } 791 792 if (isset($this->axis_valuetype[GRAPH_YAXIS_SIDE_LEFT]) || !isset($sides)) { 793 $sides[] = GRAPH_YAXIS_SIDE_LEFT; 794 } 795 796 foreach ($sides as $snum => $side) { 797 if (!isset($this->axis_valuetype[$side])) { 798 continue; 799 } 800 801 if (($this->ymin_type != GRAPH_YAXIS_TYPE_FIXED || $this->ymax_type != GRAPH_YAXIS_TYPE_CALCULATED) 802 && $this->type == GRAPH_TYPE_STACKED) { 803 $this->m_minY[$side] = min($this->m_minY[$side], 0); 804 $this->validateMinMax($this->m_minY[$side], $this->m_maxY[$side]); 805 806 continue; 807 } 808 809 if ($this->ymax_type == GRAPH_YAXIS_TYPE_FIXED) { 810 $this->m_maxY[$side] = $this->yaxismax; 811 if ($this->ymin_type == GRAPH_YAXIS_TYPE_CALCULATED 812 && ($this->m_minY[$side] == null || bccomp($this->m_maxY[$side], $this->m_minY[$side]) == 0 813 || bccomp($this->m_maxY[$side], $this->m_minY[$side]) == -1)) { 814 if ($this->m_maxY[$side] == 0) { 815 $this->m_minY[$side] = -1; 816 } 817 elseif ($this->m_maxY[$side] > 0) { 818 $this->m_minY[$side] = bcmul($this->m_maxY[$side], 0.8); 819 } 820 else { 821 $this->m_minY[$side] = bcmul($this->m_maxY[$side], 1.2); 822 } 823 } 824 } 825 826 if ($this->ymin_type == GRAPH_YAXIS_TYPE_FIXED) { 827 $this->m_minY[$side] = $this->yaxismin; 828 if ($this->ymax_type == GRAPH_YAXIS_TYPE_CALCULATED 829 && ($this->m_maxY[$side] == null || bccomp($this->m_maxY[$side], $this->m_minY[$side]) == 0 830 || bccomp($this->m_maxY[$side], $this->m_minY[$side]) == -1)) { 831 if ($this->m_minY[$side] > 0) { 832 $this->m_maxY[$side] = bcmul($this->m_minY[$side], 1.2); 833 } 834 else { 835 $this->m_maxY[$side] = bcmul($this->m_minY[$side], 0.8); 836 } 837 } 838 } 839 840 $this->validateMinMax($this->m_minY[$side], $this->m_maxY[$side]); 841 } 842 843 $side = GRAPH_YAXIS_SIDE_LEFT; 844 $other_side = GRAPH_YAXIS_SIDE_RIGHT; 845 846 // Invert sides and its bases, if left side doesn't exist. 847 if (!isset($this->axis_valuetype[GRAPH_YAXIS_SIDE_LEFT])) { 848 $side = GRAPH_YAXIS_SIDE_RIGHT; 849 $other_side = GRAPH_YAXIS_SIDE_LEFT; 850 $tempBase = $leftBase1024; 851 $leftBase1024 = $rightBase1024; 852 $rightBase1024 = $tempBase; 853 } 854 855 if (!isset($this->m_minY[$side])) { 856 $this->m_minY[$side] = 0; 857 } 858 if (!isset($this->m_maxY[$side])) { 859 $this->m_maxY[$side] = 0; 860 } 861 862 if (!isset($this->m_minY[$other_side])) { 863 $this->m_minY[$other_side] = 0; 864 } 865 if (!isset($this->m_maxY[$other_side])) { 866 $this->m_maxY[$other_side] = 0; 867 } 868 869 $tmp_minY = $this->m_minY; 870 $tmp_maxY = $this->m_maxY; 871 872 // Calculate interval. 873 $columnInterval = bcdiv(bcmul($this->gridPixelsVert, (bcsub($this->m_maxY[$side], $this->m_minY[$side]))), $this->sizeY); 874 875 $dist = bcmul(5, bcpow(10, 18)); 876 877 $interval = 0; 878 foreach ($intervals as $int) { 879 // We must get a positive number. 880 if (bccomp($int, $columnInterval) == -1) { 881 $t = bcsub($columnInterval, $int); 882 } 883 else { 884 $t = bcsub($int, $columnInterval); 885 } 886 887 if (bccomp($t, $dist) == -1) { 888 $dist = $t; 889 $interval = $int; 890 } 891 } 892 893 // Calculate interval, if left side use B or Bps. 894 if ($leftBase1024) { 895 $interval = getBase1024Interval($interval, $this->m_minY[$side], $this->m_maxY[$side]); 896 } 897 898 $columnInterval = bcdiv(bcmul($this->gridPixelsVert, bcsub($this->m_maxY[$other_side], $this->m_minY[$other_side])), $this->sizeY); 899 900 $dist = bcmul(5, bcpow(10, 18)); 901 902 $interval_other_side = 0; 903 foreach ($intervals as $int) { 904 // We must get a positive number. 905 if (bccomp($int, $columnInterval) == -1) { 906 $t = bcsub($columnInterval, $int); 907 } 908 else { 909 $t = bcsub($int, $columnInterval); 910 } 911 912 if (bccomp($t,$dist) == -1) { 913 $dist = $t; 914 $interval_other_side = $int; 915 } 916 } 917 918 // Calculate interval, if right side use B or Bps. 919 if ($rightBase1024) { 920 $interval_other_side = getBase1024Interval($interval_other_side, $this->m_minY[$other_side], 921 $this->m_maxY[$other_side]); 922 } 923 924 // Save original min and max items values. 925 foreach ($sides as $graphSide) { 926 $minY[$graphSide] = $this->m_minY[$graphSide]; 927 $maxY[$graphSide] = $this->m_maxY[$graphSide]; 928 } 929 930 if (!isset($minY[$side])) { 931 $minY[$side] = 0; 932 } 933 if (!isset($maxY[$side])) { 934 $maxY[$side] = 0; 935 } 936 937 // Correcting MIN & MAX. 938 $this->m_minY[$side] = bcmul(bcfloor(bcdiv($this->m_minY[$side], $interval)), $interval); 939 $this->m_maxY[$side] = bcmul(bcceil(bcdiv($this->m_maxY[$side], $interval)), $interval); 940 $this->m_minY[$other_side] = bcmul(bcfloor(bcdiv($this->m_minY[$other_side], $interval_other_side)), $interval_other_side); 941 $this->m_maxY[$other_side] = bcmul(bcceil(bcdiv($this->m_maxY[$other_side], $interval_other_side)), $interval_other_side); 942 943 // Add intervals so min/max Y wouldn't be too close to graph's top/bottom edges. 944 foreach ($sides as $graphSide) { 945 if ($graphSide == $side) { 946 $tmpInterval = $interval; 947 } 948 else { 949 $tmpInterval = $interval_other_side; 950 } 951 952 if (bccomp($this->m_minY[$graphSide], $minY[$side]) == 0 953 && $this->m_minY[$graphSide] != null && $this->m_minY[$graphSide] != 0) { 954 $this->m_minY[$graphSide] = bcsub($this->m_minY[$graphSide], $tmpInterval); 955 } 956 957 if (bccomp($this->m_maxY[$graphSide], $maxY[$graphSide]) == 0 958 && $this->m_maxY[$graphSide] != null && $this->m_maxY[$graphSide] != 0) { 959 $this->m_maxY[$graphSide] = bcadd($this->m_maxY[$graphSide], $tmpInterval); 960 } 961 } 962 963 // Calculate interval count for main and other side. 964 $this->gridLinesCount[$side] = bcceil(bcdiv(bcsub($this->m_maxY[$side], $this->m_minY[$side]), $interval)); 965 $this->gridLinesCount[$other_side] = bcceil(bcdiv(bcsub($this->m_maxY[$other_side], $this->m_minY[$other_side]), $interval_other_side)); 966 967 $this->m_maxY[$side] = bcadd($this->m_minY[$side], bcmul($interval, $this->gridLinesCount[$side])); 968 $this->gridStep[$side] = $interval; 969 970 if (isset($this->axis_valuetype[$other_side])) { 971 // Other side correction. 972 $dist = bcsub($this->m_maxY[$other_side], $this->m_minY[$other_side]); 973 $interval = 1; 974 975 foreach ($intervals as $int) { 976 if (bccomp($dist, bcmul($this->gridLinesCount[$side], $int)) == -1) { 977 $interval = $int; 978 break; 979 } 980 } 981 982 // Correcting MIN & MAX on other side Y axis. 983 $this->m_minY[$other_side] = bcmul(bcfloor(bcdiv($this->m_minY[$other_side], $interval)), $interval); 984 $this->m_maxY[$other_side] = bcmul(bcceil(bcdiv($this->m_maxY[$other_side], $interval)), $interval); 985 986 // Do recalculation in case if calculated min value is greater than calculated max value. 987 if (bccomp($tmp_maxY[$other_side], $this->m_maxY[$other_side]) == 1 || bccomp($tmp_minY[$other_side], $this->m_minY[$other_side]) == -1) { 988 $dist = bcsub($this->m_maxY[$other_side], $this->m_minY[$other_side]); 989 $interval = 0; 990 foreach ($intervals as $int) { 991 if (bccomp($dist, bcmul($this->gridLinesCount[$side], $int)) == -1) { 992 $interval = $int; 993 break; 994 } 995 } 996 997 // Correcting MIN & MAX values on other side Y axis. 998 $this->m_minY[$other_side] = bcmul(bcfloor(bcdiv($this->m_minY[$other_side], $interval)), $interval); 999 $this->m_maxY[$other_side] = bcmul(bcceil(bcdiv($this->m_maxY[$other_side], $interval)), $interval); 1000 } 1001 1002 // Calculate interval, if right side use B or Bps. 1003 if (isset($rightBase1024)) { 1004 $interval = getBase1024Interval($interval, $this->m_minY[$side], $this->m_maxY[$side]); 1005 // Correcting MIN & MAX values on other side Y axis. 1006 $this->m_minY[$other_side] = bcmul(bcfloor(bcdiv($this->m_minY[$other_side], $interval)), $interval); 1007 $this->m_maxY[$other_side] = bcmul(bcceil(bcdiv($this->m_maxY[$other_side], $interval)), $interval); 1008 } 1009 1010 $this->gridLinesCount[$other_side] = $this->gridLinesCount[$side]; 1011 $this->m_maxY[$other_side] = bcadd($this->m_minY[$other_side], bcmul($interval, $this->gridLinesCount[$other_side])); 1012 $this->gridStep[$other_side] = $interval; 1013 } 1014 1015 foreach ($sides as $graphSide) { 1016 if (!isset($this->axis_valuetype[$graphSide])) { 1017 continue; 1018 } 1019 1020 if ($this->type == GRAPH_TYPE_STACKED) { 1021 $this->m_minY[$graphSide] = bccomp($tmp_minY[GRAPH_YAXIS_SIDE_LEFT], 0) == -1 ? $tmp_minY[GRAPH_YAXIS_SIDE_LEFT] : 0; 1022 } 1023 1024 if ($this->ymax_type == GRAPH_YAXIS_TYPE_FIXED) { 1025 $this->m_maxY[$graphSide] = $this->yaxismax; 1026 } 1027 elseif ($this->ymax_type == GRAPH_YAXIS_TYPE_ITEM_VALUE) { 1028 $this->m_maxY[$graphSide] = $tmp_maxY[$graphSide]; 1029 } 1030 1031 if ($this->ymin_type == GRAPH_YAXIS_TYPE_FIXED) { 1032 $this->m_minY[$graphSide] = $this->yaxismin; 1033 } 1034 elseif ($this->ymin_type == GRAPH_YAXIS_TYPE_ITEM_VALUE) { 1035 $this->m_minY[$graphSide] = $tmp_minY[$graphSide]; 1036 } 1037 1038 $this->validateMinMax($this->m_minY[$graphSide], $this->m_maxY[$graphSide]); 1039 } 1040 1041 // Get diff between min/max Y values and fix potential division by zero. 1042 $diff_val = bcsub($this->m_maxY[$side], $this->m_minY[$side]); 1043 if (bccomp($diff_val, 0) == 0) { 1044 $diff_val = 1; 1045 } 1046 1047 $this->gridStepX[$side] = bcdiv(bcmul($this->gridStep[$side], $this->sizeY), $diff_val); 1048 1049 if (isset($this->axis_valuetype[$other_side])) { 1050 $diff_val = bcsub($this->m_maxY[$other_side], $this->m_minY[$other_side]); 1051 if (bccomp($diff_val, 0) == 0) { 1052 $diff_val = 1; 1053 } 1054 $this->gridStepX[$other_side] = bcdiv(bcmul($this->gridStep[$other_side], $this->sizeY), $diff_val); 1055 } 1056 } 1057 1058 /********************************************************************************************************/ 1059 // DRAW ELEMENTS 1060 /********************************************************************************************************/ 1061 public function drawXYAxisScale() { 1062 $gbColor = $this->getColor($this->graphtheme['gridbordercolor'], 0); 1063 1064 if ($this->yaxis[GRAPH_YAXIS_SIDE_LEFT]) { 1065 zbx_imageline( 1066 $this->im, 1067 $this->shiftXleft + $this->shiftXCaption, 1068 $this->shiftY - 5, 1069 $this->shiftXleft + $this->shiftXCaption, 1070 $this->sizeY + $this->shiftY + 4, 1071 $gbColor 1072 ); 1073 1074 imagefilledpolygon( 1075 $this->im, 1076 [ 1077 $this->shiftXleft + $this->shiftXCaption - 3, $this->shiftY - 5, 1078 $this->shiftXleft + $this->shiftXCaption + 3, $this->shiftY - 5, 1079 $this->shiftXleft + $this->shiftXCaption, $this->shiftY - 10 1080 ], 1081 3, 1082 $this->getColor('White') 1083 ); 1084 1085 /* draw left axis triangle */ 1086 zbx_imageline($this->im, $this->shiftXleft + $this->shiftXCaption - 3, $this->shiftY - 5, 1087 $this->shiftXleft + $this->shiftXCaption + 3, $this->shiftY - 5, 1088 $gbColor); 1089 zbx_imagealine($this->im, $this->shiftXleft + $this->shiftXCaption - 3, $this->shiftY - 5, 1090 $this->shiftXleft + $this->shiftXCaption, $this->shiftY - 10, 1091 $gbColor); 1092 zbx_imagealine($this->im, $this->shiftXleft + $this->shiftXCaption + 3, $this->shiftY - 5, 1093 $this->shiftXleft + $this->shiftXCaption, $this->shiftY - 10, 1094 $gbColor); 1095 } 1096 else { 1097 dashedLine( 1098 $this->im, 1099 $this->shiftXleft + $this->shiftXCaption, 1100 $this->shiftY, 1101 $this->shiftXleft + $this->shiftXCaption, 1102 $this->sizeY + $this->shiftY, 1103 $this->getColor($this->graphtheme['gridcolor'], 0) 1104 ); 1105 } 1106 1107 if ($this->yaxis[GRAPH_YAXIS_SIDE_RIGHT]) { 1108 zbx_imageline( 1109 $this->im, 1110 $this->sizeX + $this->shiftXleft + $this->shiftXCaption, 1111 $this->shiftY - 5, 1112 $this->sizeX + $this->shiftXleft + $this->shiftXCaption, 1113 $this->sizeY + $this->shiftY + 4, 1114 $gbColor 1115 ); 1116 1117 imagefilledpolygon( 1118 $this->im, 1119 [ 1120 $this->sizeX + $this->shiftXleft + $this->shiftXCaption - 3, $this->shiftY - 5, 1121 $this->sizeX + $this->shiftXleft + $this->shiftXCaption + 3, $this->shiftY - 5, 1122 $this->sizeX + $this->shiftXleft + $this->shiftXCaption, $this->shiftY - 10 1123 ], 1124 3, 1125 $this->getColor('White') 1126 ); 1127 1128 /* draw right axis triangle */ 1129 zbx_imageline($this->im, $this->sizeX + $this->shiftXleft + $this->shiftXCaption - 3, $this->shiftY - 5, 1130 $this->sizeX + $this->shiftXleft + $this->shiftXCaption + 3, $this->shiftY - 5, 1131 $gbColor); 1132 zbx_imagealine($this->im, $this->sizeX + $this->shiftXleft + $this->shiftXCaption + 3, $this->shiftY - 5, 1133 $this->sizeX + $this->shiftXleft + $this->shiftXCaption, $this->shiftY - 10, 1134 $gbColor); 1135 zbx_imagealine($this->im, $this->sizeX + $this->shiftXleft + $this->shiftXCaption - 3, $this->shiftY - 5, 1136 $this->sizeX + $this->shiftXleft + $this->shiftXCaption, $this->shiftY - 10, 1137 $gbColor); 1138 } 1139 else { 1140 dashedLine( 1141 $this->im, 1142 $this->sizeX + $this->shiftXleft + $this->shiftXCaption, 1143 $this->shiftY, 1144 $this->sizeX + $this->shiftXleft + $this->shiftXCaption, 1145 $this->sizeY + $this->shiftY, 1146 $this->getColor($this->graphtheme['gridcolor'], 0) 1147 ); 1148 } 1149 1150 zbx_imageline( 1151 $this->im, 1152 $this->shiftXleft + $this->shiftXCaption - 3, 1153 $this->sizeY + $this->shiftY + 1, 1154 $this->sizeX + $this->shiftXleft + $this->shiftXCaption + 5, 1155 $this->sizeY + $this->shiftY + 1, 1156 $gbColor 1157 ); 1158 1159 imagefilledpolygon( 1160 $this->im, 1161 [ 1162 $this->sizeX + $this->shiftXleft + $this->shiftXCaption + 5, $this->sizeY + $this->shiftY - 2, 1163 $this->sizeX + $this->shiftXleft + $this->shiftXCaption + 5, $this->sizeY + $this->shiftY + 4, 1164 $this->sizeX + $this->shiftXleft + $this->shiftXCaption + 10, $this->sizeY + $this->shiftY + 1 1165 ], 1166 3, 1167 $this->getColor('White') 1168 ); 1169 1170 /* draw X axis triangle */ 1171 zbx_imageline($this->im, $this->sizeX + $this->shiftXleft + $this->shiftXCaption + 5, $this->sizeY + $this->shiftY - 2, 1172 $this->sizeX + $this->shiftXleft + $this->shiftXCaption + 5, $this->sizeY + $this->shiftY + 4, 1173 $gbColor); 1174 zbx_imagealine($this->im, $this->sizeX + $this->shiftXleft + $this->shiftXCaption + 5, $this->sizeY + $this->shiftY + 4, 1175 $this->sizeX + $this->shiftXleft + $this->shiftXCaption + 10, $this->sizeY + $this->shiftY + 1, 1176 $gbColor); 1177 zbx_imagealine($this->im, $this->sizeX + $this->shiftXleft + $this->shiftXCaption + 10, $this->sizeY + $this->shiftY + 1, 1178 $this->sizeX + $this->shiftXleft + $this->shiftXCaption + 5, $this->sizeY + $this->shiftY - 2, 1179 $gbColor); 1180 } 1181 1182 /** 1183 * Draws Y scale grid. 1184 */ 1185 private function drawHorizontalGrid() { 1186 $yAxis = $this->yaxis[GRAPH_YAXIS_SIDE_LEFT] ? GRAPH_YAXIS_SIDE_LEFT : GRAPH_YAXIS_SIDE_RIGHT; 1187 1188 $stepY = $this->gridStepX[$yAxis]; 1189 1190 if ($this->gridLinesCount[$yAxis] < round($this->sizeY / $this->gridPixels)) { 1191 $stepY = $stepY / 2; 1192 } 1193 1194 $xLeft = $this->shiftXleft; 1195 $xRight = $this->shiftXleft + $this->sizeX; 1196 $lineColor = $this->getColor($this->graphtheme['gridcolor'], 0); 1197 1198 for ($y = $this->shiftY + $this->sizeY - $stepY; $y > $this->shiftY; $y -= $stepY) { 1199 dashedLine($this->im, $xLeft, $y, $xRight, $y, $lineColor); 1200 } 1201 } 1202 1203 private function drawTimeGrid() { 1204 $time_format = (date('Y', $this->stime) != date('Y', $this->to_time)) 1205 ? DATE_FORMAT 1206 : DATE_TIME_FORMAT_SHORT; 1207 1208 // Draw start date (and time) label. 1209 $this->drawStartEndTimePeriod($this->stime, $time_format, 0); 1210 $this->drawDateTimeIntervals(); 1211 1212 // Draw end date (and time) label. 1213 $this->drawStartEndTimePeriod($this->to_time, $time_format, $this->sizeX); 1214 } 1215 1216 /** 1217 * Draw start or end date (and time) label. 1218 * 1219 * @param int $value Unix time. 1220 * @param string $format Date time format. 1221 * @param int $position Position on X axis. 1222 */ 1223 private function drawStartEndTimePeriod($value, $format, $position) { 1224 $point = zbx_date2str(_($format), $value); 1225 $element = imageTextSize(8, 90, $point); 1226 imageText( 1227 $this->im, 1228 8, 1229 90, 1230 $this->shiftXleft + $position + round($element['width'] / 2), 1231 $this->sizeY + $this->shiftY + $element['height'] + 6, 1232 $this->getColor($this->graphtheme['highlightcolor'], 0), 1233 $point 1234 ); 1235 } 1236 1237 /** 1238 * Draw main period label in red color with 8px font size under X axis and a 2px dashed gray vertical line 1239 * according to that label. 1240 * 1241 * @param string $value Readable timestamp. 1242 * @param int $position Position on X axis. 1243 */ 1244 private function drawMainPeriod($value, $position) { 1245 $dims = imageTextSize(8, 90, $value); 1246 1247 imageText( 1248 $this->im, 1249 8, 1250 90, 1251 $this->shiftXleft + $position + round($dims['width'] / 2), 1252 $this->sizeY + $this->shiftY + $dims['height'] + 6, 1253 $this->getColor($this->graphtheme['highlightcolor'], 0), 1254 $value 1255 ); 1256 1257 dashedLine( 1258 $this->im, 1259 $this->shiftXleft + $position, 1260 $this->shiftY, 1261 $this->shiftXleft + $position, 1262 $this->sizeY + $this->shiftY, 1263 $this->getColor($this->graphtheme['maingridcolor'], 0) 1264 ); 1265 } 1266 1267 /** 1268 * Draw main period label in black color with 7px font size under X axis and a 1px dashed gray vertical line 1269 * according to that label. 1270 * 1271 * @param strimg $value Readable timestamp. 1272 * @param int $position Position on X axis. 1273 */ 1274 private function drawSubPeriod($value, $position) { 1275 $element = imageTextSize(7, 90, $value); 1276 1277 imageText( 1278 $this->im, 1279 7, 1280 90, 1281 $this->shiftXleft + $position + round($element['width'] / 2), 1282 $this->sizeY + $this->shiftY + $element['height'] + 6, 1283 $this->getColor($this->graphtheme['textcolor'], 0), 1284 $value 1285 ); 1286 1287 dashedLine( 1288 $this->im, 1289 $this->shiftXleft + $position, 1290 $this->shiftY, 1291 $this->shiftXleft + $position, 1292 $this->sizeY + $this->shiftY, 1293 $this->getColor($this->graphtheme['gridcolor'], 0) 1294 ); 1295 } 1296 1297 /** 1298 * Get best matching X-axis interval specification for the preferred sub-interval. 1299 * 1300 * @param int $pref_sub_interval Preferred sub-interval in seconds. 1301 * @param float $min_sub_interval Preferred minimal sub-interval in seconds (float). Discarded if no matches. 1302 * 1303 * @return array 1304 */ 1305 private function getOptimalDateTimeIntervalSpec($pref_sub_interval, $min_sub_interval) { 1306 // Possible X-axis main and sub-intervals. 1307 $intervals = [ 1308 'PT1M' => ['PT1S', 'PT5S', 'PT10S', 'PT30S'], 1309 'PT1H' => ['PT1M', 'PT2M', 'PT5M', 'PT15M', 'PT30M'], 1310 'P1D' => ['PT1H', 'PT3H', 'PT6H', 'PT12H'], 1311 'P1W' => ['P1D'], 1312 'P1M' => ['P3D', 'P1W', 'P2W'], 1313 'P1Y' => ['P1M', 'P3M', 'P4M', 'P6M'], 1314 'P10Y' => ['P1Y', 'P5Y'] 1315 ]; 1316 1317 // Starting date and time aligners. 1318 $aligners = [ 1319 'PT1M' => ['trim' => 'Y-m-d H:i:00', 'convert' => null], 1320 'PT1H' => ['trim' => 'Y-m-d H:00:00', 'convert' => null], 1321 'P1D' => ['trim' => 'Y-m-d 00:00:00', 'convert' => null], 1322 'P1W' => ['trim' => 'Y-m-d 00:00:00', 'convert' => 'last Sunday 00:00:00'], 1323 'P1M' => ['trim' => 'Y-m-01 00:00:00', 'convert' => null], 1324 'P1Y' => ['trim' => 'Y-01-01 00:00:00', 'convert' => null], 1325 'P10Y' => ['trim' => '1970-01-01 00:00:00', 'convert' => null] 1326 ]; 1327 1328 // Date and time label formats. 1329 $formats = [ 1330 'PT1M' => ['main' => TIME_FORMAT, 'sub' => _('H:i:s')], 1331 'PT1H' => ['main' => TIME_FORMAT, 'sub' => TIME_FORMAT], 1332 'P1D' => ['main' => _('m-d'), 'sub' => TIME_FORMAT], 1333 'P1W' => ['main' => _('m-d'), 'sub' => _('m-d')], 1334 'P1M' => ['main' => _('m-d'), 'sub' => _('m-d')], 1335 'P1Y' => ['main' => _x('Y', DATE_FORMAT_CONTEXT), 'sub' => _('M')], 1336 'P10Y' => ['main' => _x('Y', DATE_FORMAT_CONTEXT), 'sub' => _x('Y', DATE_FORMAT_CONTEXT)] 1337 ]; 1338 1339 $best_main_interval = null; 1340 $best_sub_interval = null; 1341 $best_sub_interval_ts = 0; 1342 $best_sub_interval_prop = INF; 1343 1344 foreach ($intervals as $main_interval => $sub_intervals) { 1345 foreach ($sub_intervals as $sub_interval) { 1346 $interval_ts = (new DateTime('@0')) 1347 ->add(new DateInterval($sub_interval)) 1348 ->getTimestamp(); 1349 1350 $interval_prop = max($pref_sub_interval, $interval_ts) / min($pref_sub_interval, $interval_ts); 1351 1352 // Search for the best interval preferably but not necessarily matching the $min_sub_interval criteria. 1353 $is_better = ($best_sub_interval_ts < $min_sub_interval) 1354 ? $interval_ts > $best_sub_interval_ts 1355 : $interval_prop < $best_sub_interval_prop; 1356 1357 if ($is_better) { 1358 $best_main_interval = $main_interval; 1359 $best_sub_interval = $sub_interval; 1360 $best_sub_interval_ts = $interval_ts; 1361 $best_sub_interval_prop = $interval_prop; 1362 } 1363 } 1364 } 1365 1366 return [ 1367 'intervals' => [ 1368 'main' => new DateInterval($best_main_interval), 1369 'sub' => new DateInterval($best_sub_interval) 1370 ], 1371 'aligner' => $aligners[$best_main_interval], 1372 'format' => $formats[$best_main_interval] 1373 ]; 1374 } 1375 1376 /** 1377 * Get date and time intervals in the given range for the X-axis. 1378 * 1379 * @param int $start Range start in seconds. 1380 * @param int $end Range end in seconds. 1381 * @param DateInterval $interval Date and time interval. 1382 * 1383 * @return array 1384 */ 1385 private function getDateTimeIntervals($start, $end, $interval) { 1386 $intervals = []; 1387 1388 $interval_ts = (new DateTime('@0')) 1389 ->add($interval) 1390 ->getTimestamp(); 1391 1392 // Manage time transitions natively. 1393 if ($interval_ts >= SEC_PER_DAY) { 1394 $dt = (new DateTime())->setTimestamp($start); 1395 1396 while ($dt->getTimestamp() <= $end) { 1397 $intervals[] = $dt->getTimestamp(); 1398 $dt->add($interval); 1399 } 1400 } 1401 else { 1402 $transitions = (new DateTime())->getTimezone()->getTransitions($start, $end); 1403 if (!$transitions) { 1404 $transitions = []; 1405 } 1406 1407 $time = $start; 1408 $transition = 1; 1409 1410 while ($time <= $end) { 1411 $correct_before = 0; 1412 $correct_after = 0; 1413 1414 while ($transition < count($transitions) && $time >= $transitions[$transition]['ts']) { 1415 $offset_diff = $transitions[$transition]['offset'] - $transitions[$transition - 1]['offset']; 1416 1417 if ($interval_ts > abs($offset_diff)) { 1418 if ($transitions[$transition]['isdst']) { 1419 if ($time - $transitions[$transition]['ts'] >= $offset_diff) { 1420 $correct_before -= $offset_diff; 1421 } 1422 else { 1423 $correct_after -= $offset_diff; 1424 } 1425 } 1426 else { 1427 $correct_before -= $offset_diff; 1428 } 1429 } 1430 1431 $transition++; 1432 } 1433 1434 $time += $correct_before; 1435 $intervals[] = $time; 1436 $time += $correct_after + $interval_ts; 1437 } 1438 } 1439 1440 return $intervals; 1441 } 1442 1443 /** 1444 * Draw date and time intervals under the X axis. 1445 */ 1446 private function drawDateTimeIntervals() { 1447 // Calculate standard label width in time units. 1448 $label_size = imageTextSize(7, 90, 'WWW')['width'] * $this->period / $this->sizeX * 2; 1449 1450 $preferred_sub_interval = (int) ($this->period * $this->gridPixels / $this->sizeX); 1451 1452 $optimal = $this->getOptimalDateTimeIntervalSpec($preferred_sub_interval, $label_size); 1453 1454 // Align starting date and time with the interval. 1455 $start = strtotime(date($optimal['aligner']['trim'], $this->stime)); 1456 if ($optimal['aligner']['convert'] !== null) { 1457 $start = strtotime($optimal['aligner']['convert'], $start); 1458 } 1459 1460 $end = $this->stime + $this->period; 1461 1462 // Draw main intervals. 1463 $mains = []; 1464 1465 foreach ($this->getDateTimeIntervals($start, $end, $optimal['intervals']['main']) as $time) { 1466 $pos = $time - $this->stime; 1467 1468 if ($pos >= $label_size && $pos <= $this->period - $label_size) { 1469 $this->drawMainPeriod(date($optimal['format']['main'], $time), $pos * $this->sizeX / $this->period); 1470 } 1471 1472 $mains[] = $time; 1473 } 1474 1475 $mains[] = $end; 1476 1477 // Draw sub-intervals. 1478 $sub_interval_ts = (new DateTime('@0')) 1479 ->add($optimal['intervals']['sub']) 1480 ->getTimestamp(); 1481 1482 // Sub-intervals' margin from the main interval boundaries. 1483 $main_margin = max($label_size, (int) ($sub_interval_ts * .75)); 1484 1485 for ($i = 0, $i_max = count($mains) - 2; $i <= $i_max; $i++) { 1486 $pos_min = $mains[$i] - $this->stime + $main_margin; 1487 $pos_max = $mains[$i + 1] - $this->stime - $main_margin; 1488 1489 foreach ($this->getDateTimeIntervals($mains[$i], $mains[$i + 1], $optimal['intervals']['sub']) as $time) { 1490 $pos = $time - $this->stime; 1491 1492 if ($pos >= max($pos_min, $label_size) && $pos <= min($pos_max, $this->period - $label_size)) { 1493 $this->drawSubPeriod(date($optimal['format']['sub'], $time), $pos * $this->sizeX / $this->period); 1494 } 1495 } 1496 } 1497 } 1498 1499 private function drawSides() { 1500 if (isset($this->axis_valuetype[GRAPH_YAXIS_SIDE_RIGHT]) 1501 && ($this->yaxis[GRAPH_YAXIS_SIDE_RIGHT] || $this->skipRightScale != 1)) { 1502 $sides[] = GRAPH_YAXIS_SIDE_RIGHT; 1503 } 1504 1505 if (((isset($this->axis_valuetype[GRAPH_YAXIS_SIDE_LEFT])) 1506 && ($this->yaxis[GRAPH_YAXIS_SIDE_LEFT] || $this->skipLeftScale != 1)) || !isset($sides)) { 1507 $sides[] = GRAPH_YAXIS_SIDE_LEFT; 1508 } 1509 1510 foreach ($sides as $side) { 1511 $minY = $this->m_minY[$side]; 1512 $maxY = $this->m_maxY[$side]; 1513 $units = null; 1514 $unitsLong = null; 1515 $byteStep = false; 1516 1517 for ($item = 0; $item < $this->num; $item++) { 1518 if ($this->items[$item]['yaxisside'] == $side) { 1519 // check if items use B or Bps units 1520 if ($this->items[$item]['units'] == 'B' || $this->items[$item]['units'] == 'Bps') { 1521 $byteStep = true; 1522 } 1523 if (is_null($units)) { 1524 $units = $this->items[$item]['units']; 1525 } 1526 elseif ($this->items[$item]['units'] != $units) { 1527 $units = ''; 1528 } 1529 } 1530 } 1531 1532 if (is_null($units) || $units === false) { 1533 $units = ''; 1534 } 1535 else { 1536 for ($item = 0; $item < $this->num; $item++) { 1537 if ($this->items[$item]['yaxisside'] == $side && !empty($this->items[$item]['unitsLong'])) { 1538 $unitsLong = $this->items[$item]['unitsLong']; 1539 break; 1540 } 1541 } 1542 } 1543 1544 if (!empty($unitsLong)) { 1545 $dims = imageTextSize(9, 90, $unitsLong); 1546 1547 $tmpY = $this->sizeY / 2 + $this->shiftY + $dims['height'] / 2; 1548 if ($tmpY < $dims['height']) { 1549 $tmpY = $dims['height'] + 6; 1550 } 1551 1552 $tmpX = $side == GRAPH_YAXIS_SIDE_LEFT ? $dims['width'] + 8 : $this->fullSizeX - $dims['width']; 1553 1554 imageText( 1555 $this->im, 1556 9, 1557 90, 1558 $tmpX, 1559 $tmpY, 1560 $this->getColor($this->graphtheme['textcolor'], 0), 1561 $unitsLong 1562 ); 1563 } 1564 1565 $step = $this->gridStep[$side]; 1566 $hstr_count = $this->gridLinesCount[$side]; 1567 1568 // ignore milliseconds if -1 <= maxY => 1 or -1 <= minY => 1 1569 $ignoreMillisec = (bccomp($maxY, -1) <= 0 || bccomp($maxY, 1) >= 0 1570 || bccomp($minY, -1) <= 0 || bccomp($minY, 1) >= 0); 1571 1572 $newPow = false; 1573 if ($byteStep) { 1574 $maxYPow = convertToBase1024($maxY, ZBX_KIBIBYTE); 1575 $minYPow = convertToBase1024($minY, ZBX_KIBIBYTE); 1576 $powStep = ZBX_KIBIBYTE; 1577 } else { 1578 $maxYPow = convertToBase1024($maxY); 1579 $minYPow = convertToBase1024($minY); 1580 $powStep = 1000; 1581 } 1582 1583 if (abs($maxYPow['pow']) > abs($minYPow['pow']) && $maxYPow['value'] != 0) { 1584 $newPow = $maxYPow['pow']; 1585 if (abs(bcdiv($minYPow['value'], bcpow($powStep, $maxYPow['pow']))) > 1000) { 1586 $newPow = $minYPow['pow']; 1587 } 1588 } 1589 if (abs($maxYPow['pow']) < abs($minYPow['pow']) && $minYPow['value'] != 0) { 1590 $newPow = $minYPow['pow']; 1591 if (abs(bcdiv($maxYPow['value'], bcpow($powStep, $minYPow['pow']))) > 1000) { 1592 $newPow = $maxYPow['pow']; 1593 } 1594 } 1595 if ($maxYPow['pow'] == $minYPow['pow']) { 1596 $newPow = $maxYPow['pow']; 1597 } 1598 1599 $maxLength = false; 1600 // get all values in y-axis if units != 's' 1601 if ($units != 's') { 1602 $calcValues = []; 1603 for ($i = 0; $i <= $hstr_count; $i++) { 1604 $hstr_count = ($hstr_count == 0) ? 1 : $hstr_count; 1605 1606 $val = bcadd(bcmul($i, $step), $minY); 1607 1608 if (bccomp(bcadd($val, bcdiv($step,2)), $maxY) == 1) { 1609 continue; 1610 } 1611 1612 $calcValues[] = convert_units([ 1613 'value' => $val, 1614 'convert' => ITEM_CONVERT_NO_UNITS, 1615 'byteStep' => $byteStep, 1616 'pow' => $newPow 1617 ]); 1618 } 1619 1620 $calcValues[] = convert_units([ 1621 'value' => $maxY, 1622 'convert' => ITEM_CONVERT_NO_UNITS, 1623 'byteStep' => $byteStep, 1624 'pow' => $newPow 1625 ]); 1626 1627 $maxLength = calcMaxLengthAfterDot($calcValues); 1628 } 1629 1630 for ($i = 0; $i <= $hstr_count; $i++) { 1631 $hstr_count = ($hstr_count == 0) ? 1 : $hstr_count; 1632 1633 $val = bcadd(bcmul($i, $step), $minY); 1634 1635 if (bccomp(bcadd($val, bcdiv($step, 2)), $maxY) == 1) { 1636 continue; 1637 } 1638 1639 $str = convert_units([ 1640 'value' => $val, 1641 'units' => $units, 1642 'convert' => ITEM_CONVERT_NO_UNITS, 1643 'byteStep' => $byteStep, 1644 'pow' => $newPow, 1645 'ignoreMillisec' => $ignoreMillisec, 1646 'length' => $maxLength 1647 ]); 1648 1649 if ($side == GRAPH_YAXIS_SIDE_LEFT) { 1650 $dims = imageTextSize(8, 0, $str); 1651 $posX = $this->shiftXleft - $dims['width'] - 9; 1652 } 1653 else { 1654 $posX = $this->sizeX + $this->shiftXleft + 12; 1655 } 1656 1657 // marker Y coordinate 1658 $posY = $this->sizeY + $this->shiftY - $this->gridStepX[$side] * $i + 4; 1659 1660 imageText( 1661 $this->im, 1662 8, 1663 0, 1664 $posX, 1665 $posY, 1666 $this->getColor($this->graphtheme['textcolor'], 0), 1667 $str 1668 ); 1669 } 1670 1671 $str = convert_units([ 1672 'value' => $maxY, 1673 'units' => $units, 1674 'convert' => ITEM_CONVERT_NO_UNITS, 1675 'byteStep' => $byteStep, 1676 'pow' => $newPow, 1677 'ignoreMillisec' => $ignoreMillisec, 1678 'length' => $maxLength 1679 ]); 1680 1681 if ($side == GRAPH_YAXIS_SIDE_LEFT) { 1682 $dims = imageTextSize(8, 0, $str); 1683 $posX = $this->shiftXleft - $dims['width'] - 9; 1684 $color = $this->getColor(GRAPH_ZERO_LINE_COLOR_LEFT); 1685 } 1686 else { 1687 $posX = $this->sizeX + $this->shiftXleft + 12; 1688 $color = $this->getColor(GRAPH_ZERO_LINE_COLOR_RIGHT); 1689 } 1690 1691 imageText( 1692 $this->im, 1693 8, 1694 0, 1695 $posX, 1696 $this->shiftY + 4, 1697 $this->getColor($this->graphtheme['textcolor'], 0), 1698 $str 1699 ); 1700 1701 if ($this->zero[$side] != $this->sizeY + $this->shiftY && $this->zero[$side] != $this->shiftY) { 1702 zbx_imageline( 1703 $this->im, 1704 $this->shiftXleft, 1705 $this->zero[$side], 1706 $this->shiftXleft + $this->sizeX, 1707 $this->zero[$side], 1708 $color 1709 ); 1710 } 1711 } 1712 } 1713 1714 protected function drawWorkPeriod() { 1715 imagefilledrectangle($this->im, 1716 $this->shiftXleft + 1, 1717 $this->shiftY, 1718 $this->sizeX + $this->shiftXleft - 1, // -2 border 1719 $this->sizeY + $this->shiftY, 1720 $this->getColor($this->graphtheme['graphcolor'], 0) 1721 ); 1722 1723 if ($this->m_showWorkPeriod != 1) { 1724 return; 1725 } 1726 if ($this->period > 8035200) { // 31*24*3600*3 (3*month*3) 1727 return; 1728 } 1729 1730 $config = select_config(); 1731 $config = CMacrosResolverHelper::resolveTimeUnitMacros([$config], ['work_period'])[0]; 1732 1733 $periods = parse_period($config['work_period']); 1734 if (!$periods) { 1735 return; 1736 } 1737 1738 imagefilledrectangle( 1739 $this->im, 1740 $this->shiftXleft + 1, 1741 $this->shiftY, 1742 $this->sizeX + $this->shiftXleft - 1, // -1 border 1743 $this->sizeY + $this->shiftY, 1744 $this->getColor($this->graphtheme['nonworktimecolor'], 0) 1745 ); 1746 1747 $now = time(); 1748 if (isset($this->stime)) { 1749 $this->from_time = $this->stime; 1750 $this->to_time = $this->stime + $this->period; 1751 } 1752 else { 1753 $this->to_time = $now; 1754 $this->from_time = $this->to_time - $this->period; 1755 } 1756 1757 $from = $this->from_time; 1758 $max_time = $this->to_time; 1759 1760 $start = find_period_start($periods, $from); 1761 $end = -1; 1762 while ($start < $max_time && $start > 0) { 1763 $end = find_period_end($periods, $start, $max_time); 1764 1765 $x1 = round((($start - $from) * $this->sizeX) / $this->period) + $this->shiftXleft; 1766 $x2 = ceil((($end - $from) * $this->sizeX) / $this->period) + $this->shiftXleft; 1767 1768 // draw rectangle 1769 imagefilledrectangle( 1770 $this->im, 1771 $x1, 1772 $this->shiftY, 1773 $x2 - 1, // -1 border 1774 $this->sizeY + $this->shiftY, 1775 $this->getColor($this->graphtheme['graphcolor'], 0) 1776 ); 1777 1778 $start = find_period_start($periods, $end); 1779 } 1780 } 1781 1782 protected function drawPercentile() { 1783 if ($this->type != GRAPH_TYPE_NORMAL) { 1784 return; 1785 } 1786 1787 foreach ($this->percentile as $side => $percentile) { 1788 if ($percentile['percent'] > 0 && $percentile['value']) { 1789 $minY = $this->m_minY[$side]; 1790 $maxY = $this->m_maxY[$side]; 1791 1792 $color = ($side == GRAPH_YAXIS_SIDE_LEFT) 1793 ? $this->graphtheme['leftpercentilecolor'] 1794 : $this->graphtheme['rightpercentilecolor']; 1795 1796 $y = $this->sizeY - (($percentile['value'] - $minY) / ($maxY - $minY)) * $this->sizeY + $this->shiftY; 1797 zbx_imageline( 1798 $this->im, 1799 $this->shiftXleft, 1800 $y, 1801 $this->sizeX + $this->shiftXleft, 1802 $y, 1803 $this->getColor($color) 1804 ); 1805 } 1806 } 1807 } 1808 1809 protected function drawTriggers() { 1810 if ($this->m_showTriggers != 1) { 1811 return; 1812 } 1813 1814 $oppColor = $this->getColor(GRAPH_TRIGGER_LINE_OPPOSITE_COLOR); 1815 1816 foreach ($this->triggers as $trigger) { 1817 $minY = $this->m_minY[$trigger['yaxisside']]; 1818 $maxY = $this->m_maxY[$trigger['yaxisside']]; 1819 1820 if ($minY >= $trigger['val'] || $trigger['val'] >= $maxY) { 1821 continue; 1822 } 1823 1824 $y = $this->sizeY - (($trigger['val'] - $minY) / ($maxY - $minY)) * $this->sizeY + $this->shiftY; 1825 $triggerColor = $this->getColor($trigger['color']); 1826 $lineStyle = [$triggerColor, $triggerColor, $triggerColor, $triggerColor, $triggerColor, $oppColor, $oppColor, $oppColor]; 1827 1828 dashedLine( $this->im, $this->shiftXleft, $y, $this->sizeX + $this->shiftXleft, $y, $lineStyle); 1829 dashedLine( $this->im, $this->shiftXleft, $y + 1, $this->sizeX + $this->shiftXleft, $y + 1, $lineStyle); 1830 } 1831 } 1832 1833 protected function drawLegend() { 1834 // if graph is small, we are not drawing legend 1835 if (!$this->drawItemsLegend) { 1836 return true; 1837 } 1838 1839 $leftXShift = 15; 1840 $units = [GRAPH_YAXIS_SIDE_LEFT => 0, GRAPH_YAXIS_SIDE_RIGHT => 0]; 1841 1842 // draw item legend 1843 $legend = new CImageTextTable($this->im, $leftXShift - 5, $this->sizeY + $this->shiftY + self::legendOffsetY); 1844 $legend->color = $this->getColor($this->graphtheme['textcolor'], 0); 1845 $legend->rowheight = 14; 1846 $legend->fontsize = 9; 1847 1848 // item legend table header 1849 $row = [ 1850 ['text' => '', 'marginRight' => 5], 1851 ['text' => ''], 1852 ['text' => ''], 1853 ['text' => _('last'), 'align' => 1, 'fontsize' => 9], 1854 ['text' => _('min'), 'align' => 1, 'fontsize' => 9], 1855 ['text' => _('avg'), 'align' => 1, 'fontsize' => 9], 1856 ['text' => _('max'), 'align' => 1, 'fontsize' => 9] 1857 ]; 1858 1859 $legend->addRow($row); 1860 $rowNum = $legend->getNumRows(); 1861 1862 $i = ($this->type == GRAPH_TYPE_STACKED) ? $this->num - 1 : 0; 1863 while ($i >= 0 && $i < $this->num) { 1864 $color = $this->getColor($this->items[$i]['color'], GRAPH_STACKED_ALFA); 1865 switch ($this->items[$i]['calc_fnc']) { 1866 case CALC_FNC_MIN: 1867 $fncRealName = _('min'); 1868 break; 1869 case CALC_FNC_MAX: 1870 $fncRealName = _('max'); 1871 break; 1872 case CALC_FNC_ALL: 1873 $fncRealName = _('all'); 1874 break; 1875 case CALC_FNC_AVG: 1876 default: 1877 $fncRealName = _('avg'); 1878 } 1879 1880 $data = &$this->data[$this->items[$i]['itemid']][$this->items[$i]['calc_type']]; 1881 1882 // draw color square 1883 if (function_exists('imagecolorexactalpha') && function_exists('imagecreatetruecolor') && @imagecreatetruecolor(1, 1)) { 1884 $colorSquare = imagecreatetruecolor(11, 11); 1885 } 1886 else { 1887 $colorSquare = imagecreate(11, 11); 1888 } 1889 1890 imagefill($colorSquare, 0, 0, $this->getColor($this->graphtheme['backgroundcolor'], 0)); 1891 imagefilledrectangle($colorSquare, 0, 0, 10, 10, $color); 1892 imagerectangle($colorSquare, 0, 0, 10, 10, $this->getColor('Black')); 1893 1894 // caption 1895 $itemCaption = $this->itemsHost 1896 ? $this->items[$i]['name_expanded'] 1897 : $this->items[$i]['hostname'].NAME_DELIMITER.$this->items[$i]['name_expanded']; 1898 1899 // draw legend of an item with data 1900 if (isset($data) && isset($data['min'])) { 1901 if ($this->items[$i]['yaxisside'] == GRAPH_YAXIS_SIDE_LEFT) { 1902 $units[GRAPH_YAXIS_SIDE_LEFT] = $this->items[$i]['units']; 1903 } 1904 else { 1905 $units[GRAPH_YAXIS_SIDE_RIGHT] = $this->items[$i]['units']; 1906 } 1907 1908 $legend->addCell($rowNum, ['image' => $colorSquare, 'marginRight' => 5]); 1909 $legend->addCell($rowNum, ['text' => $itemCaption]); 1910 $legend->addCell($rowNum, ['text' => '['.$fncRealName.']']); 1911 $legend->addCell($rowNum, [ 1912 'text' => convert_units([ 1913 'value' => $this->getLastValue($i), 1914 'units' => $this->items[$i]['units'], 1915 'convert' => ITEM_CONVERT_NO_UNITS 1916 ]), 1917 'align' => 2 1918 ]); 1919 $legend->addCell($rowNum, [ 1920 'text' => convert_units([ 1921 'value' => min($data['min']), 1922 'units' => $this->items[$i]['units'], 1923 'convert' => ITEM_CONVERT_NO_UNITS 1924 ]), 1925 'align' => 2 1926 ]); 1927 $legend->addCell($rowNum, [ 1928 'text' => convert_units([ 1929 'value' => $data['avg_orig'], 1930 'units' => $this->items[$i]['units'], 1931 'convert' => ITEM_CONVERT_NO_UNITS 1932 ]), 1933 'align' => 2 1934 ]); 1935 $legend->addCell($rowNum, [ 1936 'text' => convert_units([ 1937 'value' => max($data['max']), 1938 'units' => $this->items[$i]['units'], 1939 'convert' => ITEM_CONVERT_NO_UNITS 1940 ]), 1941 'align' => 2 1942 ]); 1943 } 1944 // draw legend of an item without data 1945 else { 1946 $legend->addCell($rowNum, ['image' => $colorSquare, 'marginRight' => 5]); 1947 $legend->addCell($rowNum, ['text' => $itemCaption]); 1948 $legend->addCell($rowNum, ['text' => '['._('no data').']']); 1949 } 1950 1951 $rowNum++; 1952 1953 // legends for stacked graphs are written in reverse order so that the order of items 1954 // matches the order of lines on the graphs 1955 if ($this->type == GRAPH_TYPE_STACKED) { 1956 $i--; 1957 } 1958 else { 1959 $i++; 1960 } 1961 } 1962 1963 $legend->draw(); 1964 1965 // if graph is small, we are not drawing percent line and trigger legends 1966 if (!$this->drawExLegend) { 1967 return true; 1968 } 1969 1970 $legend = new CImageTextTable( 1971 $this->im, 1972 $leftXShift + 10, 1973 $this->sizeY + $this->shiftY + 14 * $rowNum + self::legendOffsetY 1974 ); 1975 $legend->color = $this->getColor($this->graphtheme['textcolor'], 0); 1976 $legend->rowheight = 14; 1977 $legend->fontsize = 9; 1978 1979 // draw percentile 1980 if ($this->type == GRAPH_TYPE_NORMAL) { 1981 foreach ($this->percentile as $side => $percentile) { 1982 if ($percentile['percent'] > 0 && $this->yaxis[$side]) { 1983 $percentile['percent'] = (float) $percentile['percent']; 1984 $convertedUnit = $percentile['value'] 1985 ? convert_units([ 1986 'value' => $percentile['value'], 1987 'units' => $units[$side] 1988 ]) 1989 : '-'; 1990 $side_str = ($side == GRAPH_YAXIS_SIDE_LEFT) ? _('left') : _('right'); 1991 $legend->addCell($rowNum, [ 1992 'text' => $percentile['percent'].'th percentile: '.$convertedUnit.' ('.$side_str.')', 1993 ITEM_CONVERT_NO_UNITS 1994 ]); 1995 $color = ($side == GRAPH_YAXIS_SIDE_LEFT) 1996 ? $this->graphtheme['leftpercentilecolor'] 1997 : $this->graphtheme['rightpercentilecolor']; 1998 1999 imagefilledpolygon( 2000 $this->im, 2001 [ 2002 $leftXShift + 5, $this->sizeY + $this->shiftY + 14 * $rowNum + self::legendOffsetY, 2003 $leftXShift - 5, $this->sizeY + $this->shiftY + 14 * $rowNum + self::legendOffsetY, 2004 $leftXShift, $this->sizeY + $this->shiftY + 14 * $rowNum + self::legendOffsetY - 10 2005 ], 2006 3, 2007 $this->getColor($color) 2008 ); 2009 2010 imagepolygon( 2011 $this->im, 2012 [ 2013 $leftXShift + 5, $this->sizeY + $this->shiftY + 14 * $rowNum + self::legendOffsetY, 2014 $leftXShift - 5, $this->sizeY + $this->shiftY + 14 * $rowNum + self::legendOffsetY, 2015 $leftXShift, $this->sizeY + $this->shiftY + 14 * $rowNum + self::legendOffsetY - 10 2016 ], 2017 3, 2018 $this->getColor('Black No Alpha') 2019 ); 2020 $rowNum++; 2021 } 2022 } 2023 } 2024 2025 $legend->draw(); 2026 2027 $legend = new CImageTextTable( 2028 $this->im, 2029 $leftXShift + 10, 2030 $this->sizeY + $this->shiftY + 14 * $rowNum + self::legendOffsetY + 5 2031 ); 2032 $legend->color = $this->getColor($this->graphtheme['textcolor'], 0); 2033 $legend->rowheight = 14; 2034 $legend->fontsize = 9; 2035 2036 // draw triggers 2037 foreach ($this->triggers as $trigger) { 2038 imagefilledellipse( 2039 $this->im, 2040 $leftXShift, 2041 $this->sizeY + $this->shiftY + 14 * $rowNum + self::legendOffsetY, 2042 10, 2043 10, 2044 $this->getColor($trigger['color']) 2045 ); 2046 2047 imageellipse( 2048 $this->im, 2049 $leftXShift, 2050 $this->sizeY + $this->shiftY + 14 * $rowNum + self::legendOffsetY, 2051 10, 2052 10, 2053 $this->getColor('Black No Alpha') 2054 ); 2055 2056 $legend->addRow([ 2057 ['text' => $trigger['description']], 2058 ['text' => $trigger['constant']] 2059 ]); 2060 $rowNum++; 2061 } 2062 2063 $legend->draw(); 2064 } 2065 2066 protected function limitToBounds(&$value1, &$value2, $min, $max, $drawtype) { 2067 // fixes graph out of bounds problem 2068 if ((($value1 > ($max + $min)) && ($value2 > ($max + $min))) || ($value1 < $min && $value2 < $min)) { 2069 if (!in_array($drawtype, [GRAPH_ITEM_DRAWTYPE_FILLED_REGION, GRAPH_ITEM_DRAWTYPE_GRADIENT_LINE])) { 2070 return false; 2071 } 2072 } 2073 2074 $y_first = $value1 > ($max + $min) || $value1 < $min; 2075 $y_second = $value2 > ($max + $min) || $value2 < $min; 2076 2077 if ($y_first) { 2078 $value1 = ($value1 > ($max + $min)) ? $max + $min : $min; 2079 } 2080 2081 if ($y_second) { 2082 $value2 = ($value2 > ($max + $min)) ? $max + $min : $min; 2083 } 2084 2085 return true; 2086 } 2087 2088 protected function drawElement(&$data, $from, $to, $minX, $maxX, $minY, $maxY, $drawtype, $max_color, $avg_color, $min_color, $minmax_color, $calc_fnc, $yaxisside) { 2089 if (!isset($data['max'][$from]) || !isset($data['max'][$to])) { 2090 return; 2091 } 2092 2093 $oxy = $this->oxy[$yaxisside]; 2094 $zero = $this->zero[$yaxisside]; 2095 $unit2px = $this->unit2px[$yaxisside]; 2096 2097 $shift_min_from = $shift_min_to = 0; 2098 $shift_max_from = $shift_max_to = 0; 2099 $shift_avg_from = $shift_avg_to = 0; 2100 2101 if (isset($data['shift_min'][$from])) { 2102 $shift_min_from = $data['shift_min'][$from]; 2103 } 2104 if (isset($data['shift_min'][$to])) { 2105 $shift_min_to = $data['shift_min'][$to]; 2106 } 2107 2108 if (isset($data['shift_max'][$from])) { 2109 $shift_max_from = $data['shift_max'][$from]; 2110 } 2111 if (isset($data['shift_max'][$to])) { 2112 $shift_max_to = $data['shift_max'][$to]; 2113 } 2114 2115 if (isset($data['shift_avg'][$from])) { 2116 $shift_avg_from = $data['shift_avg'][$from]; 2117 } 2118 if (isset($data['shift_avg'][$to])) { 2119 $shift_avg_to = $data['shift_avg'][$to]; 2120 } 2121 2122 $min_from = $data['min'][$from] + $shift_min_from; 2123 $min_to = $data['min'][$to] + $shift_min_to; 2124 2125 $max_from = $data['max'][$from] + $shift_max_from; 2126 $max_to = $data['max'][$to] + $shift_max_to; 2127 2128 $avg_from = $data['avg'][$from] + $shift_avg_from; 2129 $avg_to = $data['avg'][$to] + $shift_avg_to; 2130 2131 $x1 = $from + $this->shiftXleft - 1; 2132 $x2 = $to + $this->shiftXleft; 2133 2134 $y1min = $zero - ($min_from - $oxy) / $unit2px; 2135 $y2min = $zero - ($min_to - $oxy) / $unit2px; 2136 2137 $y1max = $zero - ($max_from - $oxy) / $unit2px; 2138 $y2max = $zero - ($max_to - $oxy) / $unit2px; 2139 2140 $y1avg = $zero - ($avg_from - $oxy) / $unit2px; 2141 $y2avg = $zero - ($avg_to - $oxy) / $unit2px; 2142 2143 switch ($calc_fnc) { 2144 case CALC_FNC_MAX: 2145 $y1 = $y1max; 2146 $y2 = $y2max; 2147 $shift_from = $shift_max_from; 2148 $shift_to = $shift_max_to; 2149 break; 2150 case CALC_FNC_MIN: 2151 $y1 = $y1min; 2152 $y2 = $y2min; 2153 $shift_from = $shift_min_from; 2154 $shift_to = $shift_min_to; 2155 break; 2156 case CALC_FNC_ALL: 2157 // max 2158 $y1x = (($y1max > ($this->sizeY + $this->shiftY)) || $y1max < $this->shiftY); 2159 $y2x = (($y2max > ($this->sizeY + $this->shiftY)) || $y2max < $this->shiftY); 2160 2161 if ($y1x) { 2162 $y1max = ($y1max > ($this->sizeY + $this->shiftY)) ? $this->sizeY + $this->shiftY : $this->shiftY; 2163 } 2164 if ($y2x) { 2165 $y2max = ($y2max > ($this->sizeY + $this->shiftY)) ? $this->sizeY + $this->shiftY : $this->shiftY; 2166 } 2167 2168 // min 2169 $y1n = (($y1min > ($this->sizeY + $this->shiftY)) || $y1min < $this->shiftY); 2170 $y2n = (($y2min > ($this->sizeY + $this->shiftY)) || $y2min < $this->shiftY); 2171 2172 if ($y1n) { 2173 $y1min = ($y1min > ($this->sizeY + $this->shiftY)) ? $this->sizeY + $this->shiftY : $this->shiftY; 2174 } 2175 if ($y2n) { 2176 $y2min = ($y2min > ($this->sizeY + $this->shiftY)) ? $this->sizeY + $this->shiftY : $this->shiftY; 2177 } 2178 2179 $a[0] = $x1; 2180 $a[1] = $y1max; 2181 $a[2] = $x1; 2182 $a[3] = $y1min; 2183 $a[4] = $x2; 2184 $a[5] = $y2min; 2185 $a[6] = $x2; 2186 $a[7] = $y2max; 2187 2188 // don't use break, avg must be drawn in this statement 2189 case CALC_FNC_AVG: 2190 2191 // don't use break, avg must be drawn in this statement 2192 default: 2193 $y1 = $y1avg; 2194 $y2 = $y2avg; 2195 $shift_from = $shift_avg_from ; 2196 $shift_to = $shift_avg_to; 2197 } 2198 2199 $shift_from -= ($shift_from != 0) ? $oxy : 0; 2200 $shift_to -= ($shift_to != 0) ? $oxy : 0; 2201 2202 $y1_shift = $zero - $shift_from / $unit2px; 2203 $y2_shift = $zero - $shift_to / $unit2px; 2204 2205 if (!$this->limitToBounds($y1, $y2, $this->shiftY, $this->sizeY, $drawtype)) { 2206 return true; 2207 } 2208 if (!$this->limitToBounds($y1_shift, $y2_shift, $this->shiftY, $this->sizeY, $drawtype)) { 2209 return true; 2210 } 2211 2212 // draw main line 2213 switch ($drawtype) { 2214 case GRAPH_ITEM_DRAWTYPE_BOLD_LINE: 2215 if ($calc_fnc == CALC_FNC_ALL) { 2216 imagefilledpolygon($this->im, $a, 4, $minmax_color); 2217 if (!$y1x || !$y2x) { 2218 zbx_imagealine($this->im, $x1, $y1max, $x2, $y2max, $max_color, LINE_TYPE_BOLD); 2219 } 2220 2221 if (!$y1n || !$y2n) { 2222 zbx_imagealine($this->im, $x1, $y1min, $x2, $y2min, $min_color, LINE_TYPE_BOLD); 2223 } 2224 } 2225 2226 zbx_imagealine($this->im, $x1, $y1, $x2, $y2, $avg_color, LINE_TYPE_BOLD); 2227 break; 2228 case GRAPH_ITEM_DRAWTYPE_LINE: 2229 if ($calc_fnc == CALC_FNC_ALL) { 2230 imagefilledpolygon($this->im, $a, 4, $minmax_color); 2231 if (!$y1x || !$y2x) { 2232 zbx_imagealine($this->im, $x1, $y1max, $x2, $y2max, $max_color); 2233 } 2234 if (!$y1n || !$y2n) { 2235 zbx_imagealine($this->im, $x1, $y1min, $x2, $y2min, $min_color); 2236 } 2237 } 2238 2239 zbx_imagealine($this->im, $x1, $y1, $x2, $y2, $avg_color); 2240 break; 2241 case GRAPH_ITEM_DRAWTYPE_FILLED_REGION: 2242 $a[0] = $x1; 2243 $a[1] = $y1; 2244 $a[2] = $x1; 2245 $a[3] = $y1_shift; 2246 $a[4] = $x2; 2247 $a[5] = $y2_shift; 2248 $a[6] = $x2; 2249 $a[7] = $y2; 2250 2251 imagefilledpolygon($this->im, $a, 4, $avg_color); 2252 break; 2253 case GRAPH_ITEM_DRAWTYPE_DOT: 2254 imagefilledrectangle($this->im, $x1 - 1, $y1 - 1, $x1, $y1, $avg_color); 2255 break; 2256 case GRAPH_ITEM_DRAWTYPE_BOLD_DOT: 2257 imagefilledrectangle($this->im, $x2 - 1, $y2 - 1, $x2 + 1, $y2 + 1, $avg_color); 2258 break; 2259 case GRAPH_ITEM_DRAWTYPE_DASHED_LINE: 2260 if (function_exists('imagesetstyle')) { 2261 // use imagesetstyle+imageline instead of bugged imagedashedline 2262 $style = [$avg_color, $avg_color, IMG_COLOR_TRANSPARENT, IMG_COLOR_TRANSPARENT]; 2263 imagesetstyle($this->im, $style); 2264 zbx_imageline($this->im, $x1, $y1, $x2, $y2, IMG_COLOR_STYLED); 2265 } 2266 else { 2267 imagedashedline($this->im, $x1, $y1, $x2, $y2, $avg_color); 2268 } 2269 break; 2270 case GRAPH_ITEM_DRAWTYPE_GRADIENT_LINE: 2271 imageLine($this->im, $x1, $y1, $x2, $y2, $avg_color); // draw the initial line 2272 imageLine($this->im, $x1, $y1 - 1, $x2, $y2 - 1, $avg_color); 2273 2274 $bitmask = 255; 2275 $blue = $avg_color & $bitmask; 2276 2277 // $blue_diff = 255 - $blue; 2278 $bitmask = $bitmask << 8; 2279 $green = ($avg_color & $bitmask) >> 8; 2280 2281 // $green_diff = 255 - $green; 2282 $bitmask = $bitmask << 8; 2283 $red = ($avg_color & $bitmask) >> 16; 2284 // $red_diff = 255 - $red; 2285 2286 // note: though gradients on the chart looks ok, the formula used is completely incorrect 2287 // if you plan to fix something here, it would be better to start from scratch 2288 $maxAlpha = 110; 2289 $startAlpha = 50; 2290 $alphaRatio = $maxAlpha / ($this->sizeY - $startAlpha); 2291 2292 $diffX = $x1 - $x2; 2293 for ($i = 0; $i <= $diffX; $i++) { 2294 $Yincr = ($diffX > 0) ? (abs($y2 - $y1) / $diffX) : 0; 2295 2296 $gy = ($y1 > $y2) ? ($y2 + $Yincr * $i) : ($y2 - $Yincr * $i); 2297 $steps = $this->sizeY + $this->shiftY - $gy + 1; 2298 2299 for ($j = 0; $j < $steps; $j++) { 2300 if (($gy + $j) < ($this->shiftY + $startAlpha)) { 2301 $alpha = 0; 2302 } 2303 else { 2304 $alpha = 127 - abs(127 - ($alphaRatio * ($gy + $j - $this->shiftY - $startAlpha))); 2305 } 2306 2307 $color = imagecolorexactalpha($this->im, $red, $green, $blue, $alpha); 2308 imagesetpixel($this->im, $x2 + $i, $gy + $j, $color); 2309 } 2310 } 2311 break; 2312 } 2313 } 2314 2315 private function calcSides() { 2316 $sides = []; 2317 2318 if (array_key_exists(GRAPH_YAXIS_SIDE_RIGHT, $this->axis_valuetype)) { 2319 $sides[] = GRAPH_YAXIS_SIDE_RIGHT; 2320 } 2321 if (array_key_exists(GRAPH_YAXIS_SIDE_LEFT, $this->axis_valuetype) || !$sides) { 2322 $sides[] = GRAPH_YAXIS_SIDE_LEFT; 2323 } 2324 2325 foreach ($sides as $side) { 2326 $this->m_minY[$side] = $this->calculateMinY($side); 2327 $this->m_maxY[$side] = $this->calculateMaxY($side); 2328 2329 if ($this->m_minY[$side] === null) { 2330 $this->m_minY[$side] = 0; 2331 } 2332 if ($this->m_maxY[$side] === null) { 2333 $this->m_maxY[$side] = 1; 2334 } 2335 2336 if ($this->m_minY[$side] == $this->m_maxY[$side]) { 2337 if ($this->graphOrientation[$side] == '-') { 2338 $this->m_maxY[$side] = 0; 2339 } 2340 elseif ($this->m_minY[$side] == 0) { 2341 $this->m_maxY[$side] = 1; 2342 } 2343 else { 2344 $this->m_minY[$side] = 0; 2345 } 2346 } 2347 elseif ($this->m_minY[$side] > $this->m_maxY[$side]) { 2348 if ($this->graphOrientation[$side] == '-') { 2349 $this->m_minY[$side] = bcmul($this->m_maxY[$side], 0.2); 2350 } 2351 else { 2352 $this->m_minY[$side] = 0; 2353 } 2354 } 2355 2356 // If max Y-scale bigger min Y-scale only for 10% or less, then we don't allow Y-scale duplicate 2357 if ($this->m_maxY[$side] && $this->m_minY[$side]) { 2358 if ($this->m_minY[$side] < 0) { 2359 $absMinY = bcmul($this->m_minY[$side], '-1'); 2360 } 2361 else { 2362 $absMinY = $this->m_minY[$side]; 2363 } 2364 if ($this->m_maxY[$side] < 0) { 2365 $absMaxY = bcmul($this->m_maxY[$side], '-1'); 2366 } 2367 else { 2368 $absMaxY = $this->m_maxY[$side]; 2369 } 2370 2371 if ($absMaxY < $absMinY) { 2372 $oldAbMaxY = $absMaxY; 2373 $absMaxY = $absMinY; 2374 $absMinY = $oldAbMaxY; 2375 } 2376 } 2377 } 2378 } 2379 2380 private function calcDimentions() { 2381 $this->shiftXleft = $this->yaxis[GRAPH_YAXIS_SIDE_LEFT] ? 85 : 30; 2382 $this->shiftXright = $this->yaxis[GRAPH_YAXIS_SIDE_RIGHT] ? 85 : 30; 2383 2384 $x_offsets = $this->shiftXleft + $this->shiftXright + 1; 2385 $y_offsets = $this->shiftY + self::legendOffsetY; 2386 2387 if (!$this->with_vertical_padding) { 2388 $y_offsets -= ($this->m_showTriggers && count($this->triggers) > 0) 2389 ? static::DEFAULT_TOP_BOTTOM_PADDING / 2 2390 : static::DEFAULT_TOP_BOTTOM_PADDING; 2391 } 2392 2393 $this->fullSizeX = $this->sizeX; 2394 $this->fullSizeY = $this->sizeY; 2395 2396 if ($this->drawLegend) { 2397 // Reserve N+1 item rows, last row is used as padding for legend. 2398 $h_legend_items = 14 * $this->num + 14; 2399 $h_legend_triggers = 14 * count($this->triggers); 2400 $h_legend_percentile = 0; 2401 2402 foreach ($this->percentile as $side => $percentile) { 2403 if ($percentile['percent'] > 0 && $this->yaxis[$side]) { 2404 $h_legend_percentile += 14; 2405 } 2406 } 2407 } 2408 2409 if ($this->outer) { 2410 $this->sizeX = $this->fullSizeX - $x_offsets; 2411 $this->sizeY = $this->fullSizeY - $y_offsets; 2412 2413 if ($this->drawLegend) { 2414 if ($this->sizeY - $h_legend_items >= self::GRAPH_HEIGHT_MIN) { 2415 $this->sizeY -= $h_legend_items; 2416 $this->drawItemsLegend = true; 2417 2418 if ($this->sizeY - $h_legend_triggers - $h_legend_percentile >= self::GRAPH_HEIGHT_MIN) { 2419 $this->sizeY -= $h_legend_triggers + $h_legend_percentile; 2420 $this->drawExLegend = true; 2421 } 2422 } 2423 } 2424 } 2425 else { 2426 $this->fullSizeX += $x_offsets; 2427 $this->fullSizeY += $y_offsets; 2428 2429 if ($this->drawLegend) { 2430 $this->fullSizeY += $h_legend_items; 2431 $this->drawItemsLegend = true; 2432 2433 if ($this->sizeY >= ZBX_GRAPH_LEGEND_HEIGHT) { 2434 $this->fullSizeY += $h_legend_triggers + $h_legend_percentile; 2435 $this->drawExLegend = true; 2436 } 2437 } 2438 } 2439 } 2440 2441 public function getMinDimensions() { 2442 $min_dimentions = [ 2443 'width' => self::GRAPH_WIDTH_MIN, 2444 'height' => self::GRAPH_HEIGHT_MIN 2445 ]; 2446 2447 if ($this->outer) { 2448 $min_dimentions['width'] += $this->yaxis[GRAPH_YAXIS_SIDE_LEFT] ? 85 : 30; 2449 $min_dimentions['width'] += $this->yaxis[GRAPH_YAXIS_SIDE_RIGHT] ? 85 : 30; 2450 $min_dimentions['width'] ++; 2451 $min_dimentions['height'] += $this->shiftY + self::legendOffsetY; 2452 } 2453 2454 return $min_dimentions; 2455 } 2456 2457 /** 2458 * Expands graph item objects data: macros in item name, time units, dependent item 2459 * 2460 */ 2461 private function expandItems() { 2462 $items_cache = zbx_toHash($this->items, 'itemid'); 2463 $items = $this->items; 2464 2465 do { 2466 $master_itemids = []; 2467 2468 foreach ($items as $item) { 2469 if ($item['type'] == ITEM_TYPE_DEPENDENT && !array_key_exists($item['master_itemid'], $items_cache)) { 2470 $master_itemids[$item['master_itemid']] = true; 2471 } 2472 $items_cache[$item['itemid']] = $item; 2473 } 2474 $master_itemids = array_keys($master_itemids); 2475 2476 $items = API::Item()->get([ 2477 'output' => ['itemid', 'type', 'master_itemid', 'delay'], 2478 'itemids' => $master_itemids 2479 ]); 2480 } while ($items); 2481 2482 $update_interval_parser = new CUpdateIntervalParser(); 2483 2484 foreach ($this->items as &$graph_item) { 2485 if ($graph_item['type'] == ITEM_TYPE_DEPENDENT) { 2486 $master_item = $graph_item; 2487 2488 while ($master_item && $master_item['type'] == ITEM_TYPE_DEPENDENT) { 2489 $master_item = $items_cache[$master_item['master_itemid']]; 2490 } 2491 $graph_item['type'] = $master_item['type']; 2492 $graph_item['delay'] = $master_item['delay']; 2493 } 2494 2495 $graph_items = CMacrosResolverHelper::resolveItemNames([$graph_item]); 2496 $graph_items = CMacrosResolverHelper::resolveTimeUnitMacros($graph_items, ['delay']); 2497 $graph_item = reset($graph_items); 2498 $graph_item['name'] = $graph_item['name_expanded']; 2499 // getItemDelay will internally convert delay and flexible delay to seconds. 2500 $update_interval_parser->parse($graph_item['delay']); 2501 $graph_item['delay'] = getItemDelay($update_interval_parser->getDelay(), 2502 $update_interval_parser->getIntervals(ITEM_DELAY_FLEXIBLE) 2503 ); 2504 $graph_item['has_scheduling_intervals'] 2505 = (bool) $update_interval_parser->getIntervals(ITEM_DELAY_SCHEDULING); 2506 2507 if (strpos($graph_item['units'], ',') === false) { 2508 $graph_item['unitsLong'] = ''; 2509 } 2510 else { 2511 list($graph_item['units'], $graph_item['unitsLong']) = explode(',', $graph_item['units']); 2512 } 2513 } 2514 unset($graph_item); 2515 } 2516 2517 /** 2518 * Calculate graph dimensions and draw 1x1 pixel image placeholder. 2519 */ 2520 public function drawDimensions() { 2521 set_image_header(); 2522 2523 $this->calculateTopPadding(); 2524 $this->selectTriggers(); 2525 $this->calcDimentions(); 2526 2527 if (function_exists('imagecolorexactalpha') && function_exists('imagecreatetruecolor') 2528 && @imagecreatetruecolor(1, 1) 2529 ) { 2530 $this->im = imagecreatetruecolor(1, 1); 2531 } 2532 else { 2533 $this->im = imagecreate(1, 1); 2534 } 2535 2536 $this->initColors(); 2537 2538 imageOut($this->im); 2539 } 2540 2541 public function draw() { 2542 $debug_mode = CWebUser::getDebugMode(); 2543 if ($debug_mode) { 2544 $start_time = microtime(true); 2545 } 2546 2547 set_image_header(); 2548 $this->calculateTopPadding(); 2549 2550 // $this->sizeX is required for selectData() method 2551 $this->expandItems(); 2552 $this->selectTriggers(); 2553 $this->calcDimentions(); 2554 $this->selectData(); 2555 2556 $this->calcSides(); 2557 $this->calcPercentile(); 2558 $this->calcMinMaxInterval(); 2559 $this->calcZero(); 2560 2561 if (function_exists('imagecolorexactalpha') && function_exists('imagecreatetruecolor') && @imagecreatetruecolor(1, 1)) { 2562 $this->im = imagecreatetruecolor($this->fullSizeX, $this->fullSizeY); 2563 } 2564 else { 2565 $this->im = imagecreate($this->fullSizeX, $this->fullSizeY); 2566 } 2567 2568 $this->initColors(); 2569 $this->drawRectangle(); 2570 $this->drawHeader(); 2571 $this->drawWorkPeriod(); 2572 $this->drawTimeGrid(); 2573 $this->drawHorizontalGrid(); 2574 $this->drawXYAxisScale(); 2575 2576 $maxX = $this->sizeX; 2577 2578 if ($this->dataFrom === 'trends') { 2579 // Correct item 'delay' field value when graph data requested for trends. 2580 foreach ($this->items as &$item) { 2581 if (!$item['has_scheduling_intervals'] || $item['delay'] != 0) { 2582 $item['delay'] = max($item['delay'], SEC_PER_HOUR); 2583 } 2584 } 2585 unset($item); 2586 } 2587 2588 // for each metric 2589 for ($item = 0; $item < $this->num; $item++) { 2590 $minY = $this->m_minY[$this->items[$item]['yaxisside']]; 2591 $maxY = $this->m_maxY[$this->items[$item]['yaxisside']]; 2592 2593 $data = &$this->data[$this->items[$item]['itemid']][$this->items[$item]['calc_type']]; 2594 2595 if (!isset($data)) { 2596 continue; 2597 } 2598 2599 if ($this->type == GRAPH_TYPE_STACKED) { 2600 $drawtype = $this->items[$item]['drawtype']; 2601 $max_color = $this->getColor('ValueMax', GRAPH_STACKED_ALFA); 2602 $avg_color = $this->getColor($this->items[$item]['color'], GRAPH_STACKED_ALFA); 2603 $min_color = $this->getColor('ValueMin', GRAPH_STACKED_ALFA); 2604 $minmax_color = $this->getColor('ValueMinMax', GRAPH_STACKED_ALFA); 2605 2606 $calc_fnc = $this->items[$item]['calc_fnc']; 2607 } 2608 else { 2609 $drawtype = $this->items[$item]['drawtype']; 2610 $max_color = $this->getColor('ValueMax', GRAPH_STACKED_ALFA); 2611 $avg_color = $this->getColor($this->items[$item]['color'], GRAPH_STACKED_ALFA); 2612 $min_color = $this->getColor('ValueMin', GRAPH_STACKED_ALFA); 2613 $minmax_color = $this->getColor('ValueMinMax', GRAPH_STACKED_ALFA); 2614 2615 $calc_fnc = $this->items[$item]['calc_fnc']; 2616 } 2617 2618 // for each X 2619 $prevDraw = true; 2620 for ($i = 1, $j = 0; $i < $maxX; $i++) { // new point 2621 if ($data['count'][$i] == 0 && $i != ($maxX - 1)) { 2622 continue; 2623 } 2624 2625 $delay = $this->items[$item]['delay']; 2626 2627 if ($this->items[$item]['type'] == ITEM_TYPE_TRAPPER 2628 || ($this->items[$item]['type'] == ITEM_TYPE_ZABBIX_ACTIVE 2629 && preg_match('/^(event)?log(rt)?\[/', $this->items[$item]['key_'])) 2630 || ($this->items[$item]['has_scheduling_intervals'] && $delay == 0)) { 2631 $draw = true; 2632 } 2633 else { 2634 if ($data['clock'] === null) { 2635 $diff = 0; 2636 } 2637 else { 2638 $diff = abs($data['clock'][$i] - $data['clock'][$j]); 2639 } 2640 2641 $cell = ($this->to_time - $this->from_time) / $this->sizeX; 2642 2643 if ($cell > $delay) { 2644 $draw = ($diff < (ZBX_GRAPH_MAX_SKIP_CELL * $cell)); 2645 } 2646 else { 2647 $draw = ($diff < (ZBX_GRAPH_MAX_SKIP_DELAY * $delay)); 2648 } 2649 } 2650 2651 if (!$draw && !$prevDraw) { 2652 $draw = true; 2653 $valueDrawType = GRAPH_ITEM_DRAWTYPE_BOLD_DOT; 2654 } 2655 else { 2656 $valueDrawType = $drawtype; 2657 $prevDraw = $draw; 2658 } 2659 2660 if ($draw) { 2661 $this->drawElement( 2662 $data, 2663 $i, 2664 $j, 2665 0, 2666 $this->sizeX, 2667 $minY, 2668 $maxY, 2669 $valueDrawType, 2670 $max_color, 2671 $avg_color, 2672 $min_color, 2673 $minmax_color, 2674 $calc_fnc, 2675 $this->items[$item]['yaxisside'] 2676 ); 2677 } 2678 2679 $j = $i; 2680 } 2681 } 2682 2683 $this->drawSides(); 2684 2685 if ($this->drawLegend) { 2686 $this->drawTriggers(); 2687 $this->drawPercentile(); 2688 $this->drawLegend(); 2689 } 2690 2691 if ($debug_mode) { 2692 $str = sprintf('%0.2f', microtime(true) - $start_time); 2693 imageText($this->im, 6, 90, $this->fullSizeX - 2, $this->fullSizeY - 5, $this->getColor('Gray'), 2694 _s('Data from %1$s. Generated in %2$s sec.', $this->dataFrom, $str) 2695 ); 2696 } 2697 2698 unset($this->items, $this->data); 2699 2700 imageOut($this->im); 2701 } 2702} 2703