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