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