<?php
/*
** ZABBIX
** Copyright (C) 2000-2010 SIA Zabbix
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**/
?>
<?php

class CChart extends CGraphDraw{

	public function __construct($type = GRAPH_TYPE_NORMAL){
		parent::__construct($type);
		
		$this->yaxismin = null;
		$this->yaxismax = null;

		$this->triggers = array();

		$this->ymin_type = GRAPH_YAXIS_TYPE_CALCULATED;
		$this->ymax_type = GRAPH_YAXIS_TYPE_CALCULATED;

		$this->yaxisright=0;
		$this->yaxisleft=0;
		
		$this->skipLeftScale = 0;		// in case if left axis should be drawn but dosn't contain any data
		$this->skipRightScale = 0;		// in case if right axis should be drawn but dosn't contain any data

		$this->ymin_itemid = 0;
		$this->ymax_itemid = 0;

		$this->legendOffsetY = 90;

		$this->percentile = array(
				'left' => array(
					'percent' => 0,		// draw percentage line
					'value'	=> 0		// calculated percentage value left y axis
				),

				'right' => array(
					'percent' => 0,		// draw percentage line
					'value'	=> 0		// calculated percentage value right y axis
				)
			);

		$this->m_showWorkPeriod = 1;
		$this->m_showTriggers = 1;

		$this->zero = array();
		$this->graphOrientation = array(
				GRAPH_YAXIS_SIDE_LEFT=>'',
				GRAPH_YAXIS_SIDE_RIGHT=>''
			);

		$this->grid = array();				// vertical & horizontal grids params

		$this->gridLinesCount = array();	// How many grids to draw
		$this->gridStep = array();			// Grid step

		$this->gridPixels = 25;				// optimal grid size
		$this->gridPixelsVert = 40;			//
	}

/********************************************************************************************************/
// PRE CONFIG:	ADD / SET / APPLY
/********************************************************************************************************/
	public function updateShifts(){
		if( ($this->yaxisleft == 1) && ($this->yaxisright == 1)){
			$this->shiftXleft = 85;
			$this->shiftXright = 85;
		}
		else if($this->yaxisleft == 1){
			$this->shiftXleft = 85;
			$this->shiftXright = 30;
		}
		else if($this->yaxisright == 1){
			$this->shiftXleft = 30;
			$this->shiftXright = 85;
		}
//			$this->sizeX = $this->sizeX - $this->shiftXleft-$this->shiftXright;
	}

	public function getShifts(){
		$shifts = array();
		$shifts['shiftXleft'] = $this->shiftXleft;
		$shifts['shiftXright'] = $this->shiftXright;
		$shifts['shiftY'] = $this->shiftY;

		$shifts['height'] = $this->sizeY;
		$shifts['width'] = $this->sizeX;

	return $shifts;
	}

	public function showWorkPeriod($value){
		$this->m_showWorkPeriod = ($value==1)?1:0;
	}

	public function showTriggers($value){
		$this->m_showTriggers = ($value==1)?1:0;
	}

	public function addItem($itemid, $axis=GRAPH_YAXIS_SIDE_DEFAULT, $calc_fnc=CALC_FNC_AVG, $color=null, $drawtype=null, $type=null, $periods_cnt=null){
		if($this->type == GRAPH_TYPE_STACKED /* stacked graph */)
			$drawtype = GRAPH_ITEM_DRAWTYPE_FILLED_REGION;

		$item = get_item_by_itemid($itemid);
		$this->items[$this->num] = $item;
		$this->items[$this->num]['description'] = item_description($item);
		$this->items[$this->num]['delay'] = getItemDelay($item['delay'], $item['delay_flex']);

		if(strpos($item['units'], ',') !== false)
			list($this->items[$this->num]['units'], $this->items[$this->num]['unitsLong']) = explode(',', $item['units']);
		else
			$this->items[$this->num]['unitsLong'] = '';


		$host = get_host_by_hostid($item['hostid']);

		$this->items[$this->num]['host'] = $host['host'];
		$this->items[$this->num]['color'] = is_null($color) ? 'Dark Green' : $color;
		$this->items[$this->num]['drawtype'] = is_null($drawtype) ? GRAPH_ITEM_DRAWTYPE_LINE : $drawtype;
		$this->items[$this->num]['axisside'] = is_null($axis) ? GRAPH_YAXIS_SIDE_DEFAULT : $axis;
		$this->items[$this->num]['calc_fnc'] = is_null($calc_fnc) ? CALC_FNC_AVG : $calc_fnc;
		$this->items[$this->num]['calc_type'] = is_null($type) ? GRAPH_ITEM_SIMPLE : $type;
		$this->items[$this->num]['periods_cnt'] = is_null($periods_cnt) ? 0 : $periods_cnt;

		if($this->items[$this->num]['axisside'] == GRAPH_YAXIS_SIDE_LEFT)
			$this->yaxisleft=1;

		if($this->items[$this->num]['axisside'] == GRAPH_YAXIS_SIDE_RIGHT)
			$this->yaxisright=1;
//		SDI($this->items);

		$this->num++;
	}

	public function setGraphOrientation($value, $axisside){
		if($value < 0){
			$this->graphOrientation[$axisside] = '-';
		}
		else if(zbx_empty($this->graphOrientation[$axisside]) && ($value > 0)){
			$this->graphOrientation[$axisside] = '+';
		}
	return $this->graphOrientation[$axisside];
	}

	public function setYMinAxisType($yaxistype){
		$this->ymin_type=$yaxistype;
	}

	public function setYMaxAxisType($yaxistype){
		$this->ymax_type=$yaxistype;
	}

	public function setYAxisMin($yaxismin){
		$this->yaxismin=$yaxismin;
	}

	public function setYAxisMax($yaxismax){
		$this->yaxismax=$yaxismax;
	}

	public function setYMinItemId($itemid){
		$this->ymin_itemid=$itemid;
	}

	public function setYMaxItemId($itemid){
		$this->ymax_itemid=$itemid;
	}

	public function setLeftPercentage($percentile){
		$this->percentile['left']['percent'] = $percentile;
	}

	public function setRightPercentage($percentile){
		$this->percentile['right']['percent'] = $percentile;
	}

	protected function selectData(){

		$this->data = array();

		$now = time(NULL);

		if(!isset($this->stime)){
			$this->stime	= $now - $this->period;
		}

		$this->diffTZ = (date('Z',$this->stime) - date('Z',$this->stime + $this->period));

//		$this->stime += $this->diffTZ;

		$this->from_time	= $this->stime; // + timeZone offset
		$this->to_time		= $this->stime + $this->period; // + timeZone offset

		$p = $this->to_time - $this->from_time;		// graph size in time
		$z = $p - $this->from_time % $p;			// graphsize - mod(from_time,p) for Oracle...
		$x = $this->sizeX;						// graph size in px

		$this->itemsHost = null;
		for($i=0; $i < $this->num; $i++){

			$real_item = get_item_by_itemid($this->items[$i]['itemid']);
			if(is_null($this->itemsHost)) $this->itemsHost = $real_item['hostid'];
			else if($this->itemsHost != $real_item['hostid']) $this->itemsHost = false;


			if(!isset($this->axis_valuetype[$this->items[$i]['axisside']])){
				$this->axis_valuetype[$this->items[$i]['axisside']] = $real_item['value_type'];
			}
			else if($this->axis_valuetype[$this->items[$i]['axisside']] != $real_item['value_type']){
				$this->axis_valuetype[$this->items[$i]['axisside']] = ITEM_VALUE_TYPE_FLOAT;
			}

			$type = $this->items[$i]['calc_type'];
			if($type == GRAPH_ITEM_AGGREGATED) {
// skip current period
				$from_time	= $this->from_time - $this->period * $this->items[$i]['periods_cnt'];
				$to_time	= $this->from_time;
			}
			else {
				$from_time	= $this->from_time;
				$to_time	= $this->to_time;
			}

			$calc_field = 'round('.$x.'*(mod('.zbx_dbcast_2bigint('clock').'+'.$z.','.$p.'))/('.$p.'),0)';  /* required for 'group by' support of Oracle */

			$sql_arr = array();

			if((($real_item['history']*86400) > (time()-($this->from_time+$this->period/2))) &&			// should pick data from history or trends
				(($this->period / $this->sizeX) <= (ZBX_MAX_TREND_DIFF / ZBX_GRAPH_MAX_SKIP_CELL)))		// is reasonable to take data from history?
			{
				$this->dataFrom = 'history';
				array_push($sql_arr,
					'SELECT itemid,'.$calc_field.' as i,'.
						' count(*) as count,avg(value) as avg,min(value) as min,'.
						' max(value) as max,max(clock) as clock'.
					' FROM history '.
					' WHERE itemid='.$this->items[$i]['itemid'].
						' AND clock>='.$from_time.
						' AND clock<='.$to_time.
					' GROUP BY itemid,'.$calc_field
					,

					'SELECT itemid,'.$calc_field.' as i,'.
						' count(*) as count,avg(value) as avg,min(value) as min,'.
						' max(value) as max,max(clock) as clock'.
					' FROM history_uint '.
					' WHERE itemid='.$this->items[$i]['itemid'].
						' AND clock>='.$from_time.
						' AND clock<='.$to_time.
					' GROUP BY itemid,'.$calc_field
					);
			}
			else{
				$this->dataFrom = 'trends';
				array_push($sql_arr,
					'SELECT itemid,'.$calc_field.' as i,'.
						' sum(num) as count,avg(value_avg) as avg,min(value_min) as min,'.
						' max(value_max) as max,max(clock) as clock'.
					' FROM trends '.
					' WHERE itemid='.$this->items[$i]['itemid'].
						' AND clock>='.$from_time.
						' AND clock<='.$to_time.
					' GROUP BY itemid,'.$calc_field
					,

					'SELECT itemid,'.$calc_field.' as i,'.
						' sum(num) as count,avg(value_avg) as avg,min(value_min) as min,'.
						' max(value_max) as max,max(clock) as clock'.
					' FROM trends_uint '.
					' WHERE itemid='.$this->items[$i]['itemid'].
						' AND clock>='.$from_time.
						' AND clock<='.$to_time.
					' GROUP BY itemid,'.$calc_field
					);

				$this->items[$i]['delay'] = max($this->items[$i]['delay'],3600);
			}
//SDI($sql_arr);

			if(!isset($this->data[$this->items[$i]['itemid']]))
				$this->data[$this->items[$i]['itemid']] = array();

			if(!isset($this->data[$this->items[$i]['itemid']][$type]))
				$this->data[$this->items[$i]['itemid']][$type] = array();

			$curr_data = &$this->data[$this->items[$i]['itemid']][$type];

			$curr_data['count'] = NULL;
			$curr_data['min'] = NULL;
			$curr_data['max'] = NULL;
			$curr_data['avg'] = NULL;
			$curr_data['clock'] = NULL;

			foreach($sql_arr as $sql){
//SDI($sql);
				$result = DBselect($sql);
				while($row = DBfetch($result)){
					$idx=$row['i']-1;
					if($idx<0) continue;
/* --------------------------------------------------
	We are taking graph on 1px more than we need,
	and here we are skiping first px, because of MOD (in SELECT),
	it combines prelast point (it would be last point if not that 1px in begining)
	and first point, but we still losing prelast point :(
	but now we've got the first point.
--------------------------------------------------*/

					$curr_data['count'][$idx]	= $row['count'];
					$curr_data['min'][$idx]	= $row['min'];
					$curr_data['max'][$idx]	= $row['max'];
					$curr_data['avg'][$idx]	= $row['avg'];
					$curr_data['clock'][$idx]	= $row['clock'];
					$curr_data['shift_min'][$idx] = 0;
					$curr_data['shift_max'][$idx] = 0;
					$curr_data['shift_avg'][$idx] = 0;
				}

				$loc_min = is_array($curr_data['min'])?min($curr_data['min']):null;
				$this->setGraphOrientation($loc_min, $this->items[$i]['axisside']);
				unset($row);
			}
			$curr_data['avg_orig'] = zbx_avg($curr_data['avg']);

// calculate missed points
			$first_idx = 0;
/*
	first_idx - last existing point
	ci - current index
	cj - count of missed in one go
	dx - offset to first value (count to last existing point)
//*/

			for($ci=0,$cj=0; $ci < $this->sizeX; $ci++){
				if(!isset($curr_data['count'][$ci]) || ($curr_data['count'][$ci] == 0)){
					$curr_data['count'][$ci] = 0;
					$curr_data['shift_min'][$ci] = 0;
					$curr_data['shift_max'][$ci] = 0;
					$curr_data['shift_avg'][$ci] = 0;
					$cj++;
					continue;
				}

//				if(($cj == 0) || ($this->type != GRAPH_TYPE_STACKED)){
//					$cj = 0;
				if($cj == 0){
					continue;
				}

				$dx = $cj + 1;
				$first_idx = $ci - $dx;

				if($first_idx < 0)	$first_idx = $ci; // if no data FROM start of graph get current data as first data

				for(;$cj > 0; $cj--){
					if(($dx < ($this->sizeX/20)) && ($this->type == GRAPH_TYPE_STACKED)){
						$curr_data['count'][$ci - ($dx - $cj)] = 1;
					}

					foreach(array('clock','min','max','avg') as $var_name){
						$var = &$curr_data[$var_name];

						if($first_idx == $ci && $var_name == 'clock'){
							$var[$ci - ($dx - $cj)] = $var[$first_idx] - (($p / $this->sizeX) * ($dx - $cj));
							continue;
						}

						$dy = $var[$ci] - $var[$first_idx];
						$var[$ci - ($dx - $cj)] = bcadd($var[$first_idx] , bcdiv(($cj * $dy) , $dx));
					}
				}
			}

			if($cj > 0 && $ci > $cj){
				$dx = $cj + 1;
				$first_idx = $ci - $dx;

				for(;$cj > 0; $cj--){
					foreach(array('clock','min','max','avg') as $var_name){
						$var = &$curr_data[$var_name];

						if( $var_name == 'clock'){
							$var[$first_idx + ($dx - $cj)] = $var[$first_idx] + (($p / $this->sizeX) * ($dx - $cj));
							continue;
						}
						$var[$first_idx + ($dx - $cj)] = $var[$first_idx];
					}
				}
			}
//*/
// end of missed points calculation
		}

// calculte shift for stacked graphs
		if($this->type == GRAPH_TYPE_STACKED){
			for($i=1; $i<$this->num; $i++){
				$curr_data = &$this->data[$this->items[$i]['itemid']][$this->items[$i]['calc_type']];

				if(!isset($curr_data))	continue;

				for($j = $i-1; $j >= 0; $j--){
					if($this->items[$j]['axisside'] != $this->items[$i]['axisside']) continue;

					$prev_data = &$this->data[$this->items[$j]['itemid']][$this->items[$j]['calc_type']];

					if(!isset($prev_data))	continue;

					for($ci = 0; $ci < $this->sizeX; $ci++){
						foreach(array('min','max','avg') as $var_name){
							$shift_var_name	= 'shift_'.$var_name;
							$curr_shift	= &$curr_data[$shift_var_name];
							$curr_var	= &$curr_data[$var_name];
							$prev_shift	= &$prev_data[$shift_var_name];
							$prev_var	= &$prev_data[$var_name];
							$curr_shift[$ci] = $prev_var[$ci] + $prev_shift[$ci];
						}
					}
					break;
				}
			}
		}
		/* end calculation of stacked graphs */
	}
/********************************************************************************************************/
// CALCULATIONS
/********************************************************************************************************/

	protected function calcTriggers(){
		$this->triggers = array();
		if($this->m_showTriggers != 1) return;
//		if($this->num != 1) return; // skip multiple graphs

		$max = 3;
		$cnt = 0;

		foreach($this->items as $inum => $item){
			$sql = 'SELECT DISTINCT h.host, tr.description, tr.triggerid, tr.expression, tr.priority, tr.value '.
					' FROM triggers tr,functions f,items i, hosts h '.
					' WHERE tr.triggerid=f.triggerid '.
						" AND f.function IN ('last','min','avg','max') ".
						' AND tr.status='.TRIGGER_STATUS_ENABLED.
						' AND i.itemid=f.itemid '.
						' AND h.hostid=i.hostid '.
						' AND f.itemid='.$item['itemid'].
					' ORDER BY tr.priority';
			$db_triggers = DBselect($sql);
			while(($trigger = DBfetch($db_triggers)) && ($cnt < $max)){
				$db_fnc_cnt = DBselect('SELECT count(*) as cnt FROM functions f WHERE f.triggerid='.$trigger['triggerid']);
				$fnc_cnt = DBfetch($db_fnc_cnt);

				if($fnc_cnt['cnt'] != 1) continue;

				CUserMacro::resolveTrigger($trigger);
//SDII($trigger);
				if(!preg_match('/\{([0-9]{1,})\}([\<\>\=]{1})([0-9\.]{1,})([K|M|G]{0,1})/i', $trigger['expression'], $arr)) continue;

				$val = $arr[3];
				if(strcasecmp($arr[4],'K') == 0)	$val *= 1024;
				else if(strcasecmp($arr[4],'M') == 0)	$val *= 1048576; //1024*1024;
				else if(strcasecmp($arr[4],'G') == 0)	$val *= 1073741824; //1024*1024*1024;

				$minY = $this->m_minY[$this->items[0]['axisside']];
				$maxY = $this->m_maxY[$this->items[0]['axisside']];

//				if($val <= $minY || $val >= $maxY)	continue;
//SDI($item['itemid']);
				switch($trigger['priority']){
					case TRIGGER_SEVERITY_DISASTER:	$color = 'Priority Disaster'; break;
					case TRIGGER_SEVERITY_HIGH: $color = 'Priority High'; break;
					case TRIGGER_SEVERITY_AVERAGE: $color = 'Priority Average'; break;
					case TRIGGER_SEVERITY_WARNING: $color = 'Priority Warning'; break;
					case TRIGGER_SEVERITY_INFORMATION: $color = 'Priority Information'; break;
					default: $color = 'Priority';
				}

				array_push($this->triggers,array(
					'skipdraw' => ($val <= $minY || $val >= $maxY),
					'y' => $this->sizeY - (($val-$minY) / ($maxY-$minY)) * $this->sizeY + $this->shiftY,
					'color' => $color,
					'description' => S_TRIGGER.': '.expand_trigger_description_by_data($trigger),
					'constant' => '['.$arr[2].' '.$arr[3].$arr[4].']'
					));
				++$cnt;
			}
		}
	}

//Calculates percentages for left & right y axis
	protected function calcPercentile(){
		if($this->type != GRAPH_TYPE_NORMAL){
			return ;
		}

		$values = array(
			'left' => array(),
			'right'=> array()
		);

		$maxX = $this->sizeX;

// For each metric
		for($item = 0; $item < $this->num; $item++){
			$data = &$this->data[$this->items[$item]['itemid']][$this->items[$item]['calc_type']];

			if(!isset($data))	continue;
// For each X
			for($i = 0; $i < $maxX; $i++){  // new point
				if(($data['count'][$i] == 0) && ($i != ($maxX-1))) continue;

				$min = $data['min'][$i];
				$max = $data['max'][$i];
				$avg = $data['avg'][$i];

				switch($this->items[$item]['calc_fnc']){
					case CALC_FNC_MAX:
						$value = $max;
						break;
					case CALC_FNC_MIN:
						$value = $min;
						break;
					case CALC_FNC_ALL:
					case CALC_FNC_AVG:
					default:
						$value = $avg;
				}

				if($this->items[$item]['axisside'] == GRAPH_YAXIS_SIDE_LEFT){
					$values['left'][] = $value;
				}
				else{
					$values['right'][] = $value;
				}
			}
		}

		foreach($this->percentile as $side => $percentile){
			if(($percentile['percent']>0) && !empty($values[$side])){
				sort($values[$side]);
				$percent = (int) ((count($values[$side]) * $percentile['percent'] / 100) + 0.5);
//SDI($percent);
				$this->percentile[$side]['value'] = $values[$side][$percent];
				unset($values[$side]);
			}
//SDI($side.' : '.$this->percentile[$side]['value']);
		}
	}

// Calculation of minimum Y axis
	protected function calculateMinY($side){
		if($this->ymin_type == GRAPH_YAXIS_TYPE_FIXED){
			return $this->yaxismin;
		}
		else if($this->ymin_type == GRAPH_YAXIS_TYPE_ITEM_VALUE){
			$item = get_item_by_itemid($this->ymin_itemid);
			if($item && isset($item['lastvalue']) && !is_null($item['lastvalue']))
				return $item['lastvalue'];
		}

		$minY = null;
		for($i=0; $i<$this->num; $i++){
			if($this->items[$i]['axisside'] != $side) continue;

			foreach(array(GRAPH_ITEM_SIMPLE, GRAPH_ITEM_AGGREGATED) as $type){

				if(!isset($this->data[$this->items[$i]['itemid']][$type]))
					continue;

				$data = &$this->data[$this->items[$i]['itemid']][$type];

				if(!isset($data))	continue;

				if($type == GRAPH_ITEM_AGGREGATED)
					$calc_fnc = CALC_FNC_ALL;
				else
					$calc_fnc = $this->items[$i]['calc_fnc'];

				switch($calc_fnc){
					case CALC_FNC_ALL:	/* use min */
					case CALC_FNC_MIN:
						$val = $data['min'];
						$shift_val = $data['shift_min'];
						break;
					case CALC_FNC_MAX:
						$val = $data['max'];
						$shift_val = $data['shift_max'];
						break;
					case CALC_FNC_AVG:
					default:
						$val = $data['avg'];
						$shift_val = $data['shift_avg'];
				}

				if(!isset($val)) continue;

				if($this->type == GRAPH_TYPE_STACKED){
					$min_val_shift = min(count($val), count($shift_val));
					for($ci=0; $ci < $min_val_shift; $ci++){
						if($shift_val[$ci] < 0){						
							$val[$ci] += bcadd($shift_val[$ci], $val[$ci]);
						}
					}

				}

				if(!isset($minY)){
					if(isset($val) && count($val) > 0){
						$minY = min($val);
					}
				}
				else{
					$minY = min($minY, min($val));
				}
			}
		}

	return $minY;
	}

// Calculation of maximum Y of a side (left/right)
	protected function calculateMaxY($side){
		if($this->ymax_type==GRAPH_YAXIS_TYPE_FIXED){
			return $this->yaxismax;
		}
		else if($this->ymax_type==GRAPH_YAXIS_TYPE_ITEM_VALUE){
			$item = get_item_by_itemid($this->ymax_itemid);
			if($item && isset($item['lastvalue']) && !is_null($item['lastvalue'])){
				 return $item['lastvalue'];
			}
		}

		$maxY = null;
		for($i=0; $i<$this->num; $i++){
			if($this->items[$i]['axisside'] != $side) continue;

			foreach(array(GRAPH_ITEM_SIMPLE, GRAPH_ITEM_AGGREGATED) as $type){
				if(!isset($this->data[$this->items[$i]['itemid']][$type])) continue;

				$data = &$this->data[$this->items[$i]['itemid']][$type];

				if(!isset($data)) continue;

				if($type == GRAPH_ITEM_AGGREGATED)
					$calc_fnc = CALC_FNC_ALL;
				else
					$calc_fnc = $this->items[$i]['calc_fnc'];

				switch($calc_fnc){
					case CALC_FNC_ALL:	/* use max */
					case CALC_FNC_MAX:
						$val = $data['max'];
						$shift_val = $data['shift_max'];
						break;
					case CALC_FNC_MIN:
						$val = $data['min'];
						$shift_val = $data['shift_min'];
						break;
					case CALC_FNC_AVG:
					default:
						$val = $data['avg'];
						$shift_val = $data['shift_avg'];
				}

				if(!isset($val)) continue;

				for($ci=0; $ci < min(count($val),count($shift_val)); $ci++){
					if($data['count'][$ci] == 0) continue;

					$val[$ci] = bcadd($shift_val[$ci], $val[$ci]);
				}
//SDII($val);
				if(!isset($maxY)){
					if(isset($val) && count($val) > 0){
						$maxY = max($val);
					}
				}
				else{
					$maxY = max($maxY, max($val));
				}
			}
		}

	return $maxY;
	}

	protected function calcZero(){
		$left = GRAPH_YAXIS_SIDE_LEFT;
		$right = GRAPH_YAXIS_SIDE_RIGHT;
		$sides = array(GRAPH_YAXIS_SIDE_LEFT, GRAPH_YAXIS_SIDE_RIGHT);

		foreach($sides as $num => $side){
			$this->unit2px[$side] = ($this->m_maxY[$side] - $this->m_minY[$side])/$this->sizeY;
			if($this->unit2px[$side] == 0) $this->unit2px[$side] = 1;

			if($this->m_minY[$side]>0){
				$this->zero[$side] = $this->sizeY+$this->shiftY;
				$this->oxy[$side] = min($this->m_minY[$side],$this->m_maxY[$side]);
			}
			else if($this->m_maxY[$side]<0) {
				$this->zero[$side] = $this->shiftY;
				$this->oxy[$side] = max($this->m_minY[$side],$this->m_maxY[$side]);
			}
			else{
				$this->zero[$side] = $this->sizeY+$this->shiftY - (int)abs($this->m_minY[$side]/$this->unit2px[$side]);
				$this->oxy[$side] = 0;
			}
		}
	}

	protected function calcMinMaxInterval(){
// INIT intervals
		$intervals = array();
		foreach(array(1,2,3,4) as $num){
			$dec = pow(0.1, $num);
			foreach(array(1,2,5) as $n => $int) $intervals[] = $int * $dec;
		}
		foreach(array(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18) as $num){
			$dec = bcpow(10, $num);
			foreach(array(1,2,5) as $n => $int)	$intervals[] = bcmul($int, $dec);
		}
//------

		$sides = array(GRAPH_YAXIS_SIDE_LEFT,GRAPH_YAXIS_SIDE_RIGHT);
		foreach($sides as $snum => $side){
			if(!isset($this->axis_valuetype[$side])) continue;

			if($this->type == GRAPH_TYPE_STACKED){
				$this->m_minY[$side] = min($tmp_minY[GRAPH_YAXIS_SIDE_LEFT], 0);
				continue;
			}

			if($this->ymax_type == GRAPH_YAXIS_TYPE_FIXED){
				$this->m_maxY[$side] = $this->yaxismax;
				$this->m_minY[$side] = 0;
			}

			if($this->ymin_type == GRAPH_YAXIS_TYPE_FIXED){
				$this->m_minY[$side] = $this->yaxismin;

				if(($this->ymax_type == GRAPH_YAXIS_TYPE_CALCULATED) && ($this->m_maxY[$side] < $this->m_minY[$side])){
					$this->m_maxY[$side] = bcmul($this->m_minY[$side], 1.2);
				}
			}
		}
		
// SIDES
		$side = GRAPH_YAXIS_SIDE_LEFT;
		$other_side = GRAPH_YAXIS_SIDE_RIGHT;
		if(!isset($this->axis_valuetype[GRAPH_YAXIS_SIDE_LEFT])){
			$side = GRAPH_YAXIS_SIDE_RIGHT;
			$other_side = GRAPH_YAXIS_SIDE_LEFT;
		}

		$tmp_minY = array();
		$tmp_maxY = array();
		$tmp_minY[GRAPH_YAXIS_SIDE_LEFT] = $this->m_minY[GRAPH_YAXIS_SIDE_LEFT];
		$tmp_minY[GRAPH_YAXIS_SIDE_RIGHT] = $this->m_minY[GRAPH_YAXIS_SIDE_RIGHT];
		$tmp_maxY[GRAPH_YAXIS_SIDE_LEFT] = $this->m_maxY[GRAPH_YAXIS_SIDE_LEFT];
		$tmp_maxY[GRAPH_YAXIS_SIDE_RIGHT] = $this->m_maxY[GRAPH_YAXIS_SIDE_RIGHT];
//------

// CALC interval
		$columnInterval = ($this->gridPixelsVert*($this->m_maxY[$side] - $this->m_minY[$side]))/$this->sizeY;
		$max = $this->m_maxY[$side];

		$dist = bcmul(5, bcpow(10, 18));
		
		$interval = 0;
		foreach($intervals as $num => $int){
			$t = abs($int - $columnInterval);

			if($t<$dist){
				$dist = $t;
				$interval = $int;
			}
		}
//------

// correctin MIN & MAX
		$this->m_minY[$side] = bcmul(floor(bcdiv($this->m_minY[$side], $interval)), $interval);
		$this->m_maxY[$side] = bcmul(ceil(bcdiv($this->m_maxY[$side], $interval)), $interval);
//--------------------

		$this->gridLinesCount[$side] = ceil(($this->m_maxY[$side] - $this->m_minY[$side]) / $interval);

// we add 1 interval so max Y woun't be at most top
		if(bccomp($this->m_maxY[$side], $tmp_maxY[$side], 2) == 0){
			$this->gridLinesCount[$side]++;
		}

		$this->m_maxY[$side] = bcadd($this->m_minY[$side], bcmul($interval, $this->gridLinesCount[$side]));
		$this->gridStep[$side] = $interval;

		if(isset($this->axis_valuetype[$other_side])){
			$dist = ($this->m_maxY[$other_side] - $this->m_minY[$other_side]);
			$interval = 1;

			foreach($intervals as $num => $int){
				if($dist < bcmul($this->gridLinesCount[$side],$int)){
					$interval = $int;
					break;
				}
			}

// correcting MIN & MAX
			$this->m_minY[$other_side] = bcmul(floor(bcdiv($this->m_minY[$other_side], $interval)), $interval);
			$this->m_maxY[$other_side] = bcmul(ceil(bcdiv($this->m_maxY[$other_side], $interval)), $interval);
//--------------------

// if we lowered min more than highed max - need additional recalculating
			if(($tmp_maxY[$other_side] > $this->m_maxY[$other_side]) || ($tmp_minY[$other_side] < $this->m_minY[$other_side])){
				$dist = bcsub($this->m_maxY[$other_side],$this->m_minY[$other_side]);
				$interval = 0;
				foreach($intervals as $num => $int){
					if($dist < bcmul($this->gridLinesCount[$side],$int)){
						$interval = $int;
						break;
					}
				}

// recorrecting MIN & MAX
				$this->m_minY[$other_side] = bcmul(floor(bcdiv($this->m_minY[$other_side], $interval)), $interval);
				$this->m_maxY[$other_side] = bcmul(ceil(bcdiv($this->m_maxY[$other_side], $interval)), $interval);
//--------------------
			}

			$this->gridLinesCount[$other_side] = $this->gridLinesCount[$side];
			$this->m_maxY[$other_side] = $this->m_minY[$other_side] + bcmul($interval,$this->gridLinesCount[$other_side]);

			$this->gridStep[$other_side] = $interval;
		}

		$sides = array(GRAPH_YAXIS_SIDE_LEFT,GRAPH_YAXIS_SIDE_RIGHT);
		foreach($sides as $snum => $graphSide){
			if(!isset($this->axis_valuetype[$graphSide])) continue;

			if($this->type == GRAPH_TYPE_STACKED){
				$this->m_minY[$graphSide] = min($tmp_minY[GRAPH_YAXIS_SIDE_LEFT], 0);
			}

			if($this->ymax_type == GRAPH_YAXIS_TYPE_FIXED){
				$this->m_maxY[$graphSide] = $this->yaxismax;
				$this->m_minY[$graphSide] = 0;
			}
			else if($this->ymax_type == GRAPH_YAXIS_TYPE_ITEM_VALUE){
				$this->m_maxY[$graphSide] = $tmp_maxY[$graphSide];
			}

			if($this->ymin_type == GRAPH_YAXIS_TYPE_FIXED){
				$this->m_minY[$graphSide] = $this->yaxismin;
			}
			else if($this->ymin_type == GRAPH_YAXIS_TYPE_ITEM_VALUE){
				$this->m_minY[$graphSide] = $tmp_minY[$graphSide];
			}
		}

// division by zero
		$diff_val = ($this->m_maxY[$side] - $this->m_minY[$side]);
		if($diff_val == 0) $diff_val = 1;
		$this->gridStepX[$side] = ($this->gridStep[$side] * $this->sizeY) / $diff_val;

		if(isset($this->axis_valuetype[$other_side])){
			$diff_val = ($this->m_maxY[$other_side] - $this->m_minY[$other_side]);
			if($diff_val == 0) $diff_val = 1;
			$this->gridStepX[$other_side] = ($this->gridStep[$other_side] * $this->sizeY) / $diff_val;
		}

//SDI($this->gridStep);
//SDI($this->gridStepX);
	}

	private function calcTimeInterval(){
		$this->grid['horizontal'] = array('sub' => array(), 'main' => array());

// align to the closest human time interval
		$raw_time_interval = ($this->gridPixels*$this->period)/$this->sizeX;
		$intervals = array(
			array('main'=> 3600,'sub' => 60),				// 1 minute
			array('main'=> 3600,'sub' => 120),				// 5 minutes
			array('main'=> 3600,'sub' => 300),				// 5 minutes
			array('main'=> 3600,'sub' => 900),				// 15 minutes
			array('main'=> 3600,'sub' => 1800),				// 30 minutes
			array('main'=> 86400,'sub' => 3600),			// 1 hour
			array('main'=> 86400,'sub' => 10800),			// 3 hours
			array('main'=> 86400,'sub' => 21600),			// 6 hours
			array('main'=> 86400,'sub' => 43200),			// 12 hours
			array('main'=> 604800,'sub' => 86400),			// 1 day
			array('main'=> 1209600,'sub' => 604800),		// 1 week
			array('main'=> 2419200,'sub' => 1209600),		// 2 weeks
			array('main'=> 4838400,'sub' => 2419200),		// 4 weeks
			array('main'=> 9676800,'sub' => 4838400),		// 8 weeks
			array('main'=> 19353600,'sub' => 9676800)		// 16 weeks
		);

		$dist = 19353600; //def week;
		$sub_interval = 0;
		$main_interval = 0;

		foreach($intervals as $num => $int){
			$t = abs($int['sub']-$raw_time_interval);

			if($t<$dist){
				$dist = $t;
				$sub_interval = $int['sub'];

				$main_interval = $int['main'];
			}
		}
//------

// Sub
		$intervalX = ($sub_interval * $this->sizeX) / $this->period;

		if($sub_interval > 86400){
			$offset = (7 - date('w',$this->from_time)) * 86400;
			$offset+= $this->diffTZ;

			$next = $this->from_time + $offset;

			$offset = mktime(0, 0, 0, date('m', $next), date('d', $next), date('Y', $next)) - $this->from_time;
			$offsetX = $offset * ($this->sizeX / $this->period);
		}
		else{
			$offset = $sub_interval - (($this->from_time + date('Z', $this->from_time)) % $sub_interval);
//			$offset+= $this->diffTZ;
			$offsetX = ($offset * $this->sizeX) / $this->period;
		}

		$vline_count = floor(($this->period-$offset) / $sub_interval);

		$start_i = 0;
		if($offsetX < 12) $start_i++;

		while(($this->sizeX - ($offsetX + ($vline_count*$intervalX))) < 12){
			$vline_count--;
		}

//SDI($this->from_time);
		$sub = &$this->grid['horizontal']['sub'];
		$sub['interval'] = $sub_interval;
		$sub['linecount'] = $vline_count;
		$sub['intervalx'] = $intervalX;
		$sub['offset'] = $offset;
		$sub['offsetx'] = $offsetX;
		$sub['start'] = $start_i;
//-----

// Main
		$intervalX = ($main_interval * $this->sizeX) / $this->period;

		if($main_interval > 86400){
			$offset = (7 - date('w',$this->from_time)) * 86400;
			$offset+= $this->diffTZ;
			$next = $this->from_time + $offset;

			$offset = mktime(0, 0, 0, date('m', $next), date('d', $next), date('Y', $next)) - $this->from_time;
			$offsetX = $offset * ($this->sizeX / $this->period);
		}
		else{
			$offset = $main_interval - (($this->from_time + (date('Z', $this->from_time))) % $main_interval);
			$offset+= $this->diffTZ;
			$offsetX = $offset * ($this->sizeX / $this->period);
		}

		$vline_count = floor(($this->period-$offset) / $main_interval);

		$start_i = 0;
		if($offsetX < 12) $start_i++;

		while(($this->sizeX - ($offsetX + ($vline_count*$intervalX))) < 12){
			$vline_count--;
		}

		$main = &$this->grid['horizontal']['main'];
		$main['interval'] = $main_interval;
		$main['linecount'] = $vline_count;
		$main['intervalx'] = $intervalX;
		$main['offset'] = $offset;
		$main['offsetx'] = $offsetX;
		$main['start'] = $start_i;
//----
	}


/********************************************************************************************************/
// DRAW ELEMENTS
/********************************************************************************************************/
	public function drawXYAxisScale(){
		dashedrectangle($this->im,
			$this->shiftXleft+$this->shiftXCaption-1,
			$this->shiftY-1,
			$this->sizeX+$this->shiftXleft+$this->shiftXCaption,
			$this->sizeY+$this->shiftY+1,
			$this->getColor($this->graphtheme['gridcolor'], 0)
			);

		if($this->yaxisleft){
			imageline($this->im,
				$this->shiftXleft+$this->shiftXCaption-1,
				$this->shiftY-5,
				$this->shiftXleft+$this->shiftXCaption-1,
				$this->sizeY+$this->shiftY+4,
				$this->getColor($this->graphtheme['gridbordercolor'], 0)
				);

			imagefilledpolygon($this->im,
					array(
						$this->shiftXleft+$this->shiftXCaption-4, $this->shiftY-5,
						$this->shiftXleft+$this->shiftXCaption+2, $this->shiftY-5,
						$this->shiftXleft+$this->shiftXCaption-1, $this->shiftY-10,
					),
					3,
					$this->getColor('White')
				);

			imagepolygon($this->im,
					array(
						$this->shiftXleft+$this->shiftXCaption-4, $this->shiftY-5,
						$this->shiftXleft+$this->shiftXCaption+2, $this->shiftY-5,
						$this->shiftXleft+$this->shiftXCaption-1, $this->shiftY-10,
					),
					3,
					$this->getColor($this->graphtheme['gridbordercolor'], 0)
				);
		}

		if($this->yaxisright){
			$color = $this->getColor($this->graphtheme['gridbordercolor'], 0);

			imageline($this->im,
				$this->sizeX+$this->shiftXleft+$this->shiftXCaption,
				$this->shiftY-5,
				$this->sizeX+$this->shiftXleft+$this->shiftXCaption,
				$this->sizeY+$this->shiftY+4,
				$color
				);

			imagefilledpolygon($this->im,
					array(
						$this->sizeX+$this->shiftXleft+$this->shiftXCaption-3, $this->shiftY-5,
						$this->sizeX+$this->shiftXleft+$this->shiftXCaption+3, $this->shiftY-5,
						$this->sizeX+$this->shiftXleft+$this->shiftXCaption, $this->shiftY-10,
					),
					3,
					$this->getColor('White')
				);

			imagepolygon($this->im,
					array(
						$this->sizeX+$this->shiftXleft+$this->shiftXCaption-3, $this->shiftY-5,
						$this->sizeX+$this->shiftXleft+$this->shiftXCaption+3, $this->shiftY-5,
						$this->sizeX+$this->shiftXleft+$this->shiftXCaption, $this->shiftY-10,
					),
					3,
					$color
				);
		}

		imageline($this->im,
			$this->shiftXleft+$this->shiftXCaption-4,
			$this->sizeY+$this->shiftY+1,
			$this->sizeX+$this->shiftXleft+$this->shiftXCaption+5,
			$this->sizeY+$this->shiftY+1,
			$this->getColor($this->graphtheme['gridbordercolor'], 0)
			);

		imagefilledpolygon($this->im,
				array(
					$this->sizeX+$this->shiftXleft+$this->shiftXCaption+5, $this->sizeY+$this->shiftY-2,
					$this->sizeX+$this->shiftXleft+$this->shiftXCaption+5, $this->sizeY+$this->shiftY+4,
					$this->sizeX+$this->shiftXleft+$this->shiftXCaption+10, $this->sizeY+$this->shiftY+1,
				),
				3,
				$this->getColor('White')
			);

		imagepolygon($this->im,
				array(
					$this->sizeX+$this->shiftXleft+$this->shiftXCaption+5, $this->sizeY+$this->shiftY-2,
					$this->sizeX+$this->shiftXleft+$this->shiftXCaption+5, $this->sizeY+$this->shiftY+4,
					$this->sizeX+$this->shiftXleft+$this->shiftXCaption+10, $this->sizeY+$this->shiftY+1,
				),
				3,
				$this->getColor($this->graphtheme['gridbordercolor'], 0)
			);
	}

// Vertical grid
	private function drawVerticalGrid(){
		$hline_count = round($this->sizeY / $this->gridPixels);

		if($this->yaxisleft){
			$tmp_hlines = $this->gridLinesCount[GRAPH_YAXIS_SIDE_LEFT];
			$stepX = $this->gridStepX[GRAPH_YAXIS_SIDE_LEFT];
		}
		else{
			$tmp_hlines = $this->gridLinesCount[GRAPH_YAXIS_SIDE_RIGHT];
			$stepX = $this->gridStepX[GRAPH_YAXIS_SIDE_RIGHT];
		}

		if($tmp_hlines < $hline_count){
			$hline_count = $tmp_hlines * 2;
			$stepX = $stepX / 2;
		}
		else{
			$hline_count = $tmp_hlines;
		}

		for($i=1; $i<$hline_count; $i++){
			$y = $stepX * $i;
			if($y >= $this->sizeY) continue;

			dashedline($this->im,
					$this->shiftXleft,
					$this->sizeY - $y+$this->shiftY,
					$this->sizeX+$this->shiftXleft,
					$this->sizeY - $y+$this->shiftY,
					$this->getColor($this->graphtheme['maingridcolor'],0)
				);
		}
	}

	private function drawTimeGrid(){
		$this->calctimeInterval();
		$this->drawSubTimeGrid();
//		$this->drawMainTimeGrid();
	}

	private function drawSubTimeGrid(){
		$main_interval = $this->grid['horizontal']['main']['interval'];
		$main_intervalX = $this->grid['horizontal']['main']['intervalx'];
		$main_offset = $this->grid['horizontal']['main']['offset'];
		$main_offsetX = $this->grid['horizontal']['main']['offsetx'];

		$sub = &$this->grid['horizontal']['sub'];
		$interval = $sub['interval'];
		$vline_count = $sub['linecount'];
		$intervalX = $sub['intervalx'];

		$offset = $sub['offset'];
		$offsetX = $sub['offsetx'];
		$start_i = $sub['start'];

		if($interval == $main_interval) return;

		$test_dims = imageTextSize(7, 90, 'WWW');
		for($i=$start_i; $i<=$vline_count; $i++){
			$new_time = $this->from_time+$i*$interval+$offset;
			$new_pos = $i*$intervalX+$offsetX;

// DayLightSave
			if($interval > 3600){
				$tz = date('Z',$this->from_time) - date('Z',$new_time);
				$new_time+=$tz;
			}

// MAIN Interval Checks
			if(($interval < 3600) && (date('i',$new_time) == 0)){
				$this->DrawMainPeriod($new_time, $new_pos);
				continue;
			}

			if(($interval >= 3600) && ($interval < 86400) && (date('H',$new_time) == 0)){
				$this->DrawMainPeriod($new_time, $new_pos);
				continue;
			}

			if(($interval == 86400) && (date('N',$new_time) == 7)){
				$this->DrawMainPeriod($new_time, $new_pos);
				continue;
			}

			if(($interval > 86400) && (($i*$interval % $main_interval + $offset) == $main_offset)){
				$this->DrawMainPeriod($new_time, $new_pos);
				continue;
			}
//----------

			dashedline($this->im,
					$this->shiftXleft+$new_pos,
					$this->shiftY,
					$this->shiftXleft+$new_pos,
					$this->sizeY+$this->shiftY,
					$this->getColor($this->graphtheme['gridcolor'], 0)
			);

			if($main_intervalX < floor(($main_interval/$interval)*$intervalX)) continue;
			else if($main_intervalX < (ceil($main_interval/$interval + 1)*$test_dims['width'])) continue;

			if($interval == 86400) $date_format = S_CCHARTS_TIMELINE_DAYS_FORMAT;
			else if($interval > 86400) $date_format = S_CCHARTS_TIMELINE_MONTHDAYS_FORMAT;
			else if($interval < 86400) $date_format = S_CCHARTS_TIMELINE_HOURS_FORMAT;

			$str = zbx_date2str($date_format, $new_time);
			$dims = imageTextSize(7, 90, $str);

			imageText($this->im,
						7,
						90,
						$this->shiftXleft+$new_pos+round($dims['width']/2),
						$this->sizeY+$this->shiftY+$dims['height']+6,
						$this->getColor($this->graphtheme['textcolor'],0),
						$str
			);
		}

// First && Last
// Start
		$str = zbx_date2str(S_CCHARTS_TIMELINE_START_DATE_FORMAT,$this->stime);
		$dims = imageTextSize(8, 90, $str);
		imageText($this->im,
					8,
					90,
					$this->shiftXleft + round($dims['width']/2),
					$this->sizeY+$this->shiftY + $dims['height'] + 6,
					$this->getColor($this->graphtheme['highlightcolor'], 0),
					$str
			);

// End
		$endtime = $this->to_time;// - $this->diffTZ;

		$str = zbx_date2str(S_CCHARTS_TIMELINE_END_DATE_FORMAT,$endtime);
		$dims = imageTextSize(8, 90, $str);
		imageText($this->im,
					8,
					90,
					$this->sizeX+$this->shiftXleft + round($dims['width']/2),
					$this->sizeY+$this->shiftY + $dims['height'] + 6,
					$this->getColor($this->graphtheme['highlightcolor'], 0),
					$str
			);
	}

	private function drawMainPeriod($new_time, $new_pos){
		if(date('H',$new_time) == 0){
			if(date('Hi', $new_time) == 0) $date_format = S_CCHARTS_TIMELINE_MAINPERIOD_MONTHDAYS_FORMAT;
			else $date_format = S_CCHARTS_TIMELINE_MAINPERIOD_FULL_DAY_TIME_FORMAT;

			$color = $this->graphtheme['highlightcolor'];
		}
		else{
			$date_format = S_CCHARTS_TIMELINE_MAINPERIOD_HOURS_FORMAT;
			$color = $this->graphtheme['highlightcolor'];
		}

		$str = zbx_date2str($date_format, $new_time);
		$dims = imageTextSize(8, 90, $str);

		imageText($this->im,
					8,
					90,
					$this->shiftXleft+$new_pos+round($dims['width']/2),
					$this->sizeY+$this->shiftY+$dims['height']+6,
					$this->getColor($color,0),
					$str
		);

		dashedline($this->im,
				$this->shiftXleft+$new_pos,
				$this->shiftY,
				$this->shiftXleft+$new_pos,
				$this->sizeY+$this->shiftY,
				$this->getColor($this->graphtheme['maingridcolor'], 0)
		);
	}

	private function drawLeftSide(){
		if(($this->yaxisleft == 0) || ($this->skipLeftScale == 1)) return;

		$minY = $this->m_minY[GRAPH_YAXIS_SIDE_LEFT];
		$maxY = $this->m_maxY[GRAPH_YAXIS_SIDE_LEFT];

		$units = null;
		$unitsLong = null;
		for($item=0; $item<$this->num; $item++){
			if($this->items[$item]['axisside'] == GRAPH_YAXIS_SIDE_LEFT){
				if(is_null($units)) $units = $this->items[$item]['units'];
				else if($this->items[$item]['units'] != $units) $units = false;
			}
		}

		if(is_null($units) || ($units === false)){
			$units = '';
		}
		else{
			for($item=0; $item<$this->num; $item++){
				if(($this->items[$item]['axisside'] == GRAPH_YAXIS_SIDE_LEFT) && !empty($this->items[$item]['unitsLong'])){
					$unitsLong = $this->items[$item]['unitsLong'];
					break;
				}
			}
		}

		if(!empty($unitsLong)){
			$dims = imageTextSize(9, 90, $unitsLong);

			$tmpY = $this->sizeY/2+$this->shiftY+$dims['height']/2;
			if($tmpY < $dims['height']) $tmpY = $dims['height'] + 6;
			
			imageText($this->im,
				9,
				90,
				$dims['width']+8,
				$tmpY,
				$this->getColor($this->graphtheme['textcolor'], 0),
				$unitsLong
			);
		}

		$step = $this->gridStep[GRAPH_YAXIS_SIDE_LEFT];
		$hstr_count = $this->gridLinesCount[GRAPH_YAXIS_SIDE_LEFT];
		for($i=0; $i<=$hstr_count; $i++){
// division by zero
			$hstr_count = ($hstr_count == 0)?1:$hstr_count;

			$val = $i * $step + $minY;
			if(bccomp(bcadd($val, bcdiv($step,2)), $maxY) == 1) continue;

			$str = convert_units($val, $units, ITEM_CONVERT_NO_UNITS);
			$dims = imageTextSize(8, 0, $str);

			imageText($this->im,
				8,
				0,
				$this->shiftXleft - $dims['width'] - 6,
				$this->sizeY - $this->gridStepX[GRAPH_YAXIS_SIDE_LEFT]*$i + $this->shiftY  + 4,
				$this->getColor($this->graphtheme['textcolor'], 0),
				$str
			);
		}

		$str = convert_units($maxY, $units, ITEM_CONVERT_NO_UNITS);
		$dims = imageTextSize(8, 0, $str);
		imageText($this->im,
			8,
			0,
			$this->shiftXleft - $dims['width'] - 6,
			$this->shiftY  + 4,
			$this->getColor($this->graphtheme['textcolor'], 0),
			$str
		);

		if(($this->zero[GRAPH_YAXIS_SIDE_LEFT] != ($this->sizeY+$this->shiftY)) && ($this->zero[GRAPH_YAXIS_SIDE_LEFT] != $this->shiftY)){
			imageline($this->im,
						$this->shiftXleft,
						$this->zero[GRAPH_YAXIS_SIDE_LEFT],
						$this->shiftXleft+$this->sizeX,
						$this->zero[GRAPH_YAXIS_SIDE_LEFT],
						$this->getColor(GRAPH_ZERO_LINE_COLOR_LEFT)
					);
		}
	}

	private function drawRightSide(){
		if(($this->yaxisright == 0) || ($this->skipRightScale == 1)) return;

		$minY = $this->m_minY[GRAPH_YAXIS_SIDE_RIGHT];
		$maxY = $this->m_maxY[GRAPH_YAXIS_SIDE_RIGHT];

		$units = null;
		$unitsLong = null;
		for($item=0; $item<$this->num; $item++){
			if($this->items[$item]['axisside'] == GRAPH_YAXIS_SIDE_RIGHT){
				if(is_null($units)) $units = $this->items[$item]['units'];
				else if($this->items[$item]['units'] != $units) $units = false;
			}
		}

		if(is_null($units) || ($units === false)){
			$units = '';
		}
		else{
			for($item=0; $item<$this->num; $item++){
				if(($this->items[$item]['axisside'] == GRAPH_YAXIS_SIDE_RIGHT) && !empty($this->items[$item]['unitsLong'])){
					$unitsLong = $this->items[$item]['unitsLong'];
					break;
				}
			}
		}

		if(!empty($unitsLong)){
			$dims = imageTextSize(9, 90, $unitsLong);

			$tmpY = $this->sizeY/2+$this->shiftY+$dims['height']/2;
			if($tmpY < $dims['height']) $tmpY = $dims['height'] + 6;
			
			imageText($this->im,
				9,
				90,
				$this->fullSizeX - $dims['width'],
				$tmpY,
				$this->getColor($this->graphtheme['textcolor'], 0),
				$unitsLong
			);
		}


		$step = $this->gridStep[GRAPH_YAXIS_SIDE_RIGHT];
		$hstr_count = $this->gridLinesCount[GRAPH_YAXIS_SIDE_RIGHT];
		for($i=0;$i<=$hstr_count;$i++){
			if($hstr_count == 0) continue;

			$val = $i * $step + $minY;
			if(bccomp(bcadd($val, bcdiv($step,2)), $maxY) == 1) continue;

			$str = convert_units($val, $units, ITEM_CONVERT_NO_UNITS);

			imageText($this->im,
				8,
				0,
				$this->sizeX+$this->shiftXleft+12,
				$this->sizeY - $this->gridStepX[GRAPH_YAXIS_SIDE_RIGHT]*$i + $this->shiftY + 4,
				$this->getColor($this->graphtheme['textcolor'], 0),
				$str
			);
		}

		$str = convert_units($maxY, $units, ITEM_CONVERT_NO_UNITS);
		imageText($this->im,
			8,
			0,
			$this->sizeX+$this->shiftXleft+12,
			$this->shiftY + 4,
			$this->getColor($this->graphtheme['textcolor'], 0),
			$str
		);

		if(($this->zero[GRAPH_YAXIS_SIDE_RIGHT] != $this->sizeY+$this->shiftY) &&
			($this->zero[GRAPH_YAXIS_SIDE_RIGHT] != $this->shiftY))
		{
			imageline($this->im,
						$this->shiftXleft,
						$this->zero[GRAPH_YAXIS_SIDE_RIGHT],
						$this->shiftXleft+$this->sizeX,
						$this->zero[GRAPH_YAXIS_SIDE_RIGHT],
						$this->getColor(GRAPH_ZERO_LINE_COLOR_RIGHT)
					); //*/
		}
	}

	protected function drawWorkPeriod(){
		imagefilledrectangle($this->im,
			$this->shiftXleft+1,
			$this->shiftY,
			$this->sizeX+$this->shiftXleft-2,	// -2 border
			$this->sizeY+$this->shiftY,
			$this->getColor($this->graphtheme['graphcolor'], 0));

		if($this->m_showWorkPeriod != 1) return;
		if($this->period > 3*2678400) return; // > 31*24*3600 (3*month)

		$db_work_period = DBselect('SELECT work_period FROM config');
		$work_period = DBfetch($db_work_period);
		if(!$work_period) return;

		$periods = parse_period($work_period['work_period']);
		if(!$periods) return;

		imagefilledrectangle($this->im,
			$this->shiftXleft+1,
			$this->shiftY,
			$this->sizeX+$this->shiftXleft-2,	// -2 border
			$this->sizeY+$this->shiftY,
			$this->getColor($this->graphtheme['noneworktimecolor'], 0)
		);

		$now = time();
		if(isset($this->stime)){
			$this->from_time=$this->stime;
			$this->to_time=$this->stime+$this->period;
		}
		else{
			$this->to_time=$now-3600*$this->from;
			$this->from_time=$this->to_time-$this->period;
		}

		$from = $this->from_time;
		$max_time = $this->to_time;

		$start = find_period_start($periods,$from);
		$end = -1;
		while(($start < $max_time) && ($start > 0)){
			$end = find_period_end($periods,$start,$max_time);

			$x1 = round((($start-$from)*$this->sizeX)/$this->period) + $this->shiftXleft;
			$x2 = ceil((($end-$from)*$this->sizeX)/$this->period) + $this->shiftXleft;


//draw rectangle
			imagefilledrectangle(
				$this->im,
				$x1,
				$this->shiftY,
				$x2-2,		// -2 border
				$this->sizeY+$this->shiftY,
				$this->getColor($this->graphtheme['graphcolor'], 0));

			$start = find_period_start($periods,$end);
		}
	}


	protected function drawPercentile(){
		if($this->type != GRAPH_TYPE_NORMAL){
			return ;
		}

		foreach($this->percentile as $side => $percentile){
			if(($percentile['percent']>0) && $percentile['value']){
				if($side == 'left'){
					$minY = $this->m_minY[GRAPH_YAXIS_SIDE_LEFT];
					$maxY = $this->m_maxY[GRAPH_YAXIS_SIDE_LEFT];

					$color = $this->graphtheme['leftpercentilecolor'];
				}
				else{
					$minY = $this->m_minY[GRAPH_YAXIS_SIDE_RIGHT];
					$maxY = $this->m_maxY[GRAPH_YAXIS_SIDE_RIGHT];

					$color = $this->graphtheme['rightpercentilecolor'];
				}

				$y = $this->sizeY - (($percentile['value']-$minY) / ($maxY-$minY)) * $this->sizeY + $this->shiftY;
				imageline(
					$this->im,
					$this->shiftXleft,
					$y,
					$this->sizeX+$this->shiftXleft,
					$y,
					$this->getColor($color));
			}
		}
	}

	protected function drawTriggers(){
		if($this->m_showTriggers != 1) return;
//		if($this->num != 1) return; // skip multiple graphs

		$opposite = hex2rgb(GRAPH_TRIGGER_LINE_OPPOSITE_COLOR);
		$oppColor = imagecolorallocate($this->im, $opposite[0], $opposite[1], $opposite[2]);
		foreach($this->triggers as $tnum => $trigger){
			if($trigger['skipdraw']) continue;

			$triggerColor = $this->getColor($trigger['color']);
			$lineStyle = Array($triggerColor, $triggerColor, $triggerColor, $triggerColor, $triggerColor, $oppColor, $oppColor, $oppColor);

//			SDI($trigger['color']);
			dashedline(
				$this->im,
				$this->shiftXleft,
				$trigger['y'],
				$this->sizeX+$this->shiftXleft,
				$trigger['y'],
				$lineStyle);
			dashedline(
				$this->im,
				$this->shiftXleft,
				$trigger['y']+1,
				$this->sizeX+$this->shiftXleft,
				$trigger['y']+1,
				$lineStyle);
		}
	}


	protected function drawLegend(){
		$leftXShift = 20;

		$units = array('left'=>0, 'right'=>0 );

		$legend = new CImageTextTable($this->im, $leftXShift+10, $this->sizeY+$this->shiftY+$this->legendOffsetY);
		$legend->color = $this->getColor($this->graphtheme['textcolor'], 0);
		$legend->rowheight = 14;
		$legend->fontsize = 9;

		$row = array(
				array('text' => ''),
				array('text' => ''),
				array('text' => S_LST_SMALL, 'align'=> 1, 'fontsize' => 9),
				array('text' => S_MIN_SMALL, 'align'=> 1, 'fontsize' => 9),
				array('text' => S_AVG_SMALL, 'align'=> 1, 'fontsize' => 9),
				array('text' => S_MAX_SMALL, 'align'=> 1, 'fontsize' => 9)
			);

		$legend->addRow($row);
		$colNum = $legend->getNumRows();

		$i = ($this->type == GRAPH_TYPE_STACKED)?($this->num-1):0;
		while(($i>=0) && ($i<$this->num)){
			$row = array();

			if($this->items[$i]['calc_type'] == GRAPH_ITEM_AGGREGATED){
				$fnc_name = 'agr('.$this->items[$i]['periods_cnt'].')';
				$color = $this->getColor('HistoryMinMax');
			}
			else{
				$color = $this->getColor($this->items[$i]['color'], GRAPH_STACKED_ALFA);
				switch($this->items[$i]['calc_fnc']){
					case CALC_FNC_MIN:	$fnc_name = S_MIN_SMALL;	break;
					case CALC_FNC_MAX:	$fnc_name = S_MAX_SMALL;	break;
					case CALC_FNC_ALL:	$fnc_name = S_ALL_SMALL;	break;
					case CALC_FNC_AVG:
					default:		$fnc_name = S_AVG_SMALL;
				}
			}

			$data = &$this->data[$this->items[$i]['itemid']][$this->items[$i]['calc_type']];

			if($this->itemsHost) $item_caption = $this->items[$i]['description'];
			else $item_caption = $this->items[$i]['host'].': '.$this->items[$i]['description'];

			if(isset($data) && isset($data['min'])){
				if($this->items[$i]['axisside'] == GRAPH_YAXIS_SIDE_LEFT)
					$units['left'] = $this->items[$i]['units'];
				else
					$units['right'] = $this->items[$i]['units'];

				$legend->addCell($colNum, array('text' => $item_caption));
				$legend->addCell($colNum, array('text' => '['.$fnc_name.']'));
				$legend->addCell($colNum, array('text' => convert_units($this->getLastValue($i),$this->items[$i]['units'],ITEM_CONVERT_NO_UNITS), 'align'=> 2));
				$legend->addCell($colNum, array('text' => convert_units(min($data['min']),$this->items[$i]['units'], ITEM_CONVERT_NO_UNITS), 'align'=> 2));
				$legend->addCell($colNum, array('text' => convert_units($data['avg_orig'],$this->items[$i]['units'], ITEM_CONVERT_NO_UNITS), 'align'=> 2));
				$legend->addCell($colNum, array('text' => convert_units(max($data['max']),$this->items[$i]['units'], ITEM_CONVERT_NO_UNITS), 'align'=> 2));
			}
			else{
				$legend->addCell($colNum,array('text' => $item_caption));
//				$legend->addCell($colNum,array('text' => $this->items[$i]['host'].': '.$this->items[$i]['description']));
				$legend->addCell($colNum,array('text' => '[ '. S_NO_DATA_SMALL . ' ]'));
			}

			imagefilledrectangle($this->im,
							$leftXShift - 5,
							$this->sizeY+$this->shiftY+14*$colNum+$this->legendOffsetY-10,
							$leftXShift + 5,
							$this->sizeY+$this->shiftY+14*$colNum+$this->legendOffsetY,
							$color
						);

			imagerectangle($this->im,
							$leftXShift - 5,
							$this->sizeY+$this->shiftY+14*$colNum+$this->legendOffsetY-10,
							$leftXShift + 5,
							$this->sizeY+$this->shiftY+14*$colNum+$this->legendOffsetY,
							$this->getColor('Black')
						);

			$colNum++;

			if($this->type == GRAPH_TYPE_STACKED) $i--;
			else $i++;
		}

		$legend->draw();

		if($this->sizeY < 120){
			return true;
		}

		$legend = new CImageTextTable(
				$this->im,
				$leftXShift+10,
				$this->sizeY+$this->shiftY+14*$colNum+$this->legendOffsetY
			);
		$legend->color = $this->getColor($this->graphtheme['textcolor'], 0);
		$legend->rowheight = 14;
		$legend->fontsize = 9;

// Draw percentile
		if($this->type == GRAPH_TYPE_NORMAL){
			foreach($this->percentile as $side => $percentile){
				if(($percentile['percent']>0) && $percentile['value']){

					$str = '%sth percentile: %s';
					$percentile['percent'] = (float) $percentile['percent'];
					$legend->addCell($colNum,array('text' => $percentile['percent'].'th percentile: '.convert_units($percentile['value'], $units[$side]).'  ('.$side.')', ITEM_CONVERT_NO_UNITS));
					if($side == 'left'){
						$color = $this->graphtheme['leftpercentilecolor'];
					}
					else{
						$color = $this->graphtheme['rightpercentilecolor'];
					}

 					imagefilledpolygon($this->im,
							array(
								$leftXShift+5,$this->sizeY+$this->shiftY+14*$colNum+$this->legendOffsetY,
								$leftXShift-5,$this->sizeY+$this->shiftY+14*$colNum+$this->legendOffsetY,
								$leftXShift,$this->sizeY+$this->shiftY+14*$colNum+$this->legendOffsetY - 10,
							),
							3,
							$this->getColor($color)
						);

					imagepolygon($this->im,
							array(
								$leftXShift+5,$this->sizeY+$this->shiftY+14*$colNum+$this->legendOffsetY,
								$leftXShift-5,$this->sizeY+$this->shiftY+14*$colNum+$this->legendOffsetY,
								$leftXShift,$this->sizeY+$this->shiftY+14*$colNum+$this->legendOffsetY - 10,
							),
							3,
							$this->getColor('Black No Alpha')
						);



					$colNum++;
				}
			}
		}

		$legend->draw();

		$legend = new CImageTextTable(
				$this->im,
				$leftXShift+10,
				$this->sizeY+$this->shiftY+14*$colNum+$this->legendOffsetY + 5
			);
		$legend->color = $this->getColor($this->graphtheme['textcolor'], 0);
		$legend->rowheight = 14;
		$legend->fontsize = 9;

// Draw triggers
//SDII($this->triggers);
		foreach($this->triggers as $trigger){
			imagefilledellipse($this->im,
				$leftXShift,
				$this->sizeY+$this->shiftY+14*$colNum+$this->legendOffsetY,
				10,
				10,
				$this->getColor($trigger['color']));

			imageellipse($this->im,
				$leftXShift,
				$this->sizeY+$this->shiftY+14*$colNum+$this->legendOffsetY,
				10,
				10,
				$this->getColor('Black No Alpha'));

//			$legend->addCell($colNum,array('text' => $trigger['description']));
//			$legend->addCell($colNum, array('text' => $trigger['constant']));

			$legend->addRow(array(
				array('text' => $trigger['description']),
				array('text' => $trigger['constant'])
				));
			$colNum++;
		}

		$legend->draw();
	}

	protected function limitToBounds(&$value1, &$value2, $min, $max, $drawtype){
// Fixes graph out of bounds problem
		if( (($value1 > ($max+$min)) && ($value2 > ($max+$min))) || (($value1 < $min) && ($value2 < $min)) ){
			if($drawtype != GRAPH_ITEM_DRAWTYPE_FILLED_REGION) return false;
		}

		$y_first = (($value1 > ($max+$min)) || ($value1 < $min));
		$y_second = (($value2 > ($max+$min)) || ($value2 < $min));

		if($y_first){
			$value1 = ($value1 > ($max+$min))?($max+$min):$min;
		}

		if($y_second){
			$value2 = ($value2 > ($max+$min))?($max+$min):$min;
		}
//--------
	return true;
	}

	protected function drawElement(&$data, $from, $to, $minX, $maxX, $minY, $maxY, $drawtype, $max_color, $avg_color, $min_color, $minmax_color, $calc_fnc, $axisside){
		if(!isset($data['max'][$from]) || !isset($data['max'][$to])) return;

		$oxy = $this->oxy[$axisside];
		$zero = $this->zero[$axisside];
		$unit2px = $this->unit2px[$axisside];
//SDI($oxy);
		$shift_min_from = $shift_min_to = 0;
		$shift_max_from = $shift_max_to = 0;
		$shift_avg_from = $shift_avg_to = 0;

		if(isset($data['shift_min'][$from]))	$shift_min_from = $data['shift_min'][$from];
		if(isset($data['shift_min'][$to]))	$shift_min_to = $data['shift_min'][$to];

		if(isset($data['shift_max'][$from]))	$shift_max_from = $data['shift_max'][$from];
		if(isset($data['shift_max'][$to]))	$shift_max_to = $data['shift_max'][$to];

		if(isset($data['shift_avg'][$from]))	$shift_avg_from = $data['shift_avg'][$from];
		if(isset($data['shift_avg'][$to]))	$shift_avg_to = $data['shift_avg'][$to];
/**/
		$min_from	= $data['min'][$from]	+ $shift_min_from;
		$min_to		= $data['min'][$to]	+ $shift_min_to;

		$max_from	= $data['max'][$from]	+ $shift_max_from;
		$max_to		= $data['max'][$to]	+ $shift_max_to;

		$avg_from	= $data['avg'][$from]	+ $shift_avg_from;
		$avg_to		= $data['avg'][$to]	+ $shift_avg_to;

		$x1 = $from + $this->shiftXleft - 1;
		$x2 = $to + $this->shiftXleft;


		$y1min = $zero - ($min_from-$oxy)/$unit2px;
		$y2min = $zero - ($min_to-$oxy)/$unit2px;
//SDI(array($y1min,$zero,$min_from,$oxy,$unit2px));
		$y1max = $zero - ($max_from-$oxy)/$unit2px;
		$y2max = $zero - ($max_to-$oxy)/$unit2px;

		$y1avg = $zero - ($avg_from-$oxy)/$unit2px;
		$y2avg = $zero - ($avg_to-$oxy)/$unit2px;		//*/

		switch($calc_fnc){
			case CALC_FNC_MAX:
				$y1 = $y1max;
				$y2 = $y2max;
				$shift_from	= $shift_max_from;
				$shift_to	= $shift_max_to;
				break;
			case CALC_FNC_MIN:
				$y1 = $y1min;
				$y2 = $y2min;
				$shift_from	= $shift_min_from;
				$shift_to	= $shift_min_to;
				break;
			case CALC_FNC_ALL:
// MAX
				$y1x = (($y1max > ($this->sizeY+$this->shiftY)) || ($y1max < $this->shiftY));
				$y2x = (($y2max > ($this->sizeY+$this->shiftY)) || ($y2max < $this->shiftY));

				if($y1x) $y1max = ($y1max > ($this->sizeY+$this->shiftY))?($this->sizeY+$this->shiftY):$this->shiftY;
				if($y2x) $y2max = ($y2max > ($this->sizeY+$this->shiftY))?($this->sizeY+$this->shiftY):$this->shiftY;
//--
// MIN
				$y1n = (($y1min > ($this->sizeY+$this->shiftY)) || ($y1min < $this->shiftY));
				$y2n = (($y2min > ($this->sizeY+$this->shiftY)) || ($y2min < $this->shiftY));

				if($y1n) $y1min = ($y1min > ($this->sizeY+$this->shiftY))?($this->sizeY+$this->shiftY):$this->shiftY;
				if($y2n) $y2min = ($y2min > ($this->sizeY+$this->shiftY))?($this->sizeY+$this->shiftY):$this->shiftY;
//--
				$a[0] = $x1;		$a[1] = $y1max;
				$a[2] = $x1;		$a[3] = $y1min;
				$a[4] = $x2;		$a[5] = $y2min;
				$a[6] = $x2;		$a[7] = $y2max;
//SDI('2: '.$x2.' - '.$x1.' : '.$y2min.' - '.$y1min);
// don't use break, avg must be drawed in this statement
			case CALC_FNC_AVG:
// don't use break, avg must be drawed in this statement
			default:
				$y1 = $y1avg;
				$y2 = $y2avg;
				$shift_from	= $shift_avg_from ;
				$shift_to	= $shift_avg_to;
		}

		$shift_from -= ($shift_from != 0)?($oxy):(0);
		$shift_to -= ($shift_to != 0)?($oxy):(0);

		$y1_shift	= $zero - $shift_from/$unit2px;
		$y2_shift	= $zero - $shift_to/$unit2px;//*/


		if(!$this->limitToBounds($y1, $y2, $this->shiftY, $this->sizeY, $drawtype)) return true;
		if(!$this->limitToBounds($y1_shift, $y2_shift, $this->shiftY, $this->sizeY, $drawtype)) return true;


// draw main line
		switch($drawtype){
			case GRAPH_ITEM_DRAWTYPE_BOLD_LINE:
				if($calc_fnc == CALC_FNC_ALL){
					imagefilledpolygon($this->im,$a,4,$minmax_color);
					if(!$y1x || !$y2x){
						imageline($this->im,$x1+1,$y1max,$x2+1,$y2max,$max_color);
						imageline($this->im,$x1,$y1max,$x2,$y2max,$max_color);
					}

					if(!$y1n || !$y2n){
						imageline($this->im,$x1-1,$y1min,$x2-1,$y2min,$min_color);
						imageline($this->im,$x1,$y1min,$x2,$y2min,$min_color);
					}
				}

				imageline($this->im,$x1,$y1+1,$x2,$y2+1,$avg_color);
				imageline($this->im,$x1,$y1,$x2,$y2,$avg_color);
				break;
			case GRAPH_ITEM_DRAWTYPE_LINE:
//SDI(array($this->im,$x1,$y1,$x2,$y2,$avg_color));
				if($calc_fnc == CALC_FNC_ALL){
					imagefilledpolygon($this->im,$a,4,$minmax_color);
					if(!$y1x || !$y2x) imageline($this->im,$x1,$y1max,$x2,$y2max,$max_color);
					if(!$y1n || !$y2n) imageline($this->im,$x1,$y1min,$x2,$y2min,$min_color);
				}

				imageline($this->im,$x1,$y1,$x2,$y2,$avg_color);
				break;
			case GRAPH_ITEM_DRAWTYPE_FILLED_REGION:
//--
				$a[0] = $x1;		$a[1] = $y1;
				$a[2] = $x1;		$a[3] = $y1_shift;
				$a[4] = $x2;		$a[5] = $y2_shift;
				$a[6] = $x2;		$a[7] = $y2;

				imagefilledpolygon($this->im,$a,4,$avg_color);
//				imageline($this->im,$x1,$y1,$x2,$y2,$this->getShadow('333333',50));
				break;
			case GRAPH_ITEM_DRAWTYPE_DOT:
				imagefilledrectangle($this->im,$x1-1,$y1-1,$x1,$y1,$avg_color);
//				imagefilledrectangle($this->im,$x2-1,$y2-1,$x2,$y2,$avg_color);
				break;
			case GRAPH_ITEM_DRAWTYPE_BOLD_DOT:
//				imagefilledrectangle($this->im,$x1-1,$y1-1,$x1+1,$y1+1,$avg_color);
				imagefilledrectangle($this->im,$x2-1,$y2-1,$x2+1,$y2+1,$avg_color);
				break;
			case GRAPH_ITEM_DRAWTYPE_DASHED_LINE:
				if( function_exists('imagesetstyle') ){
// Use imagesetstyle+imageline instead of bugged imagedashedline
					$style = array($avg_color, $avg_color, IMG_COLOR_TRANSPARENT, IMG_COLOR_TRANSPARENT);
					imagesetstyle($this->im, $style);
					imageline($this->im,$x1,$y1,$x2,$y2,IMG_COLOR_STYLED);
				}
				else{
					imagedashedline($this->im,$x1,$y1,$x2,$y2,$avg_color);
				}
				break;
			case GRAPH_ITEM_DRAWTYPE_GRADIENT_LINE:

				ImageLine($this->im, $x1, $y1, $x2, $y2, $avg_color);  //draw the initial line
				ImageLine($this->im, $x1, $y1-1, $x2, $y2-1, $avg_color);

				$bitmask = 255;
				$blue = $avg_color&$bitmask;
				// $blue_diff = 255 - $blue;
				$bitmask = $bitmask<<8;
				$green = ($avg_color&$bitmask)>>8;
				// $green_diff = 255 - $green;
				$bitmask = $bitmask<<8;
				$red = ($avg_color&$bitmask)>>16;
				// $red_diff = 255 - $red;

				$maxAlpha = 110;
				$startAlpha = 50;
				$alphaRatio = $maxAlpha / ($this->sizeY - $startAlpha);

				$diffX = $x1 - $x2;
//sdi('x1: '.$x1.'  x2: '.$x2);
				for($i=0; $i<=$diffX; $i++){
					$Yincr = ($diffX > 0)?(abs($y2 - $y1) / $diffX):0;

					$gy = ($y1 > $y2) ? ($y2 + $Yincr*$i) : ($y2 - $Yincr*$i);
					$steps = $this->sizeY + $this->shiftY - $gy + 1;

					for($j=0; $j<$steps; $j++){
						if(($gy + $j) < ($this->shiftY + $startAlpha)){
							$alpha = 0;
						}
						else{
							$alpha = 127 - abs(127 - ($alphaRatio * ($gy + $j - $this->shiftY - $startAlpha)));
						}

						$color = imagecolorexactalpha($this->im, $red, $green, $blue, $alpha);
						imagesetpixel($this->im, $x2 + $i, $gy + $j, $color);
					}
				}
			break;
		}
	}

	public function draw(){
		$start_time=getmicrotime();

		set_image_header();

		check_authorisation();

		$this->selectData();

		$this->m_minY[GRAPH_YAXIS_SIDE_LEFT]	= $this->calculateMinY(GRAPH_YAXIS_SIDE_LEFT);
		$this->m_minY[GRAPH_YAXIS_SIDE_RIGHT]	= $this->calculateMinY(GRAPH_YAXIS_SIDE_RIGHT);
		$this->m_maxY[GRAPH_YAXIS_SIDE_LEFT]	= $this->calculateMaxY(GRAPH_YAXIS_SIDE_LEFT);
		$this->m_maxY[GRAPH_YAXIS_SIDE_RIGHT]	= $this->calculateMaxY(GRAPH_YAXIS_SIDE_RIGHT);
//*
		if($this->m_minY[GRAPH_YAXIS_SIDE_LEFT] == $this->m_maxY[GRAPH_YAXIS_SIDE_LEFT]){
			if($this->graphOrientation[GRAPH_YAXIS_SIDE_LEFT] == '-') $this->m_maxY[GRAPH_YAXIS_SIDE_LEFT] = 0;
			else if($this->m_minY[GRAPH_YAXIS_SIDE_LEFT] == 0){
				$this->m_maxY[GRAPH_YAXIS_SIDE_LEFT] = 1;
//				$this->skipLeftScale = 1;
			}
			else $this->m_minY[GRAPH_YAXIS_SIDE_LEFT] = 0;
		}
		else if($this->m_minY[GRAPH_YAXIS_SIDE_LEFT] > $this->m_maxY[GRAPH_YAXIS_SIDE_LEFT]){
			if($this->graphOrientation[GRAPH_YAXIS_SIDE_LEFT] == '-'){
				$this->m_minY[GRAPH_YAXIS_SIDE_LEFT] = 0.2 * $this->m_maxY[GRAPH_YAXIS_SIDE_LEFT];
			}
			else $this->m_minY[GRAPH_YAXIS_SIDE_LEFT] = 0;
		}

		if($this->m_minY[GRAPH_YAXIS_SIDE_RIGHT] == $this->m_maxY[GRAPH_YAXIS_SIDE_RIGHT]){
			if($this->graphOrientation[GRAPH_YAXIS_SIDE_RIGHT] == '-') $this->m_maxY[GRAPH_YAXIS_SIDE_RIGHT] = 0;
			else if($this->m_minY[GRAPH_YAXIS_SIDE_RIGHT] == 0){
				$this->m_maxY[GRAPH_YAXIS_SIDE_RIGHT] = 1;
//				$this->skipRightScale = 1;
			}
			else $this->m_minY[GRAPH_YAXIS_SIDE_RIGHT] = 0;
		}
		else if($this->m_minY[GRAPH_YAXIS_SIDE_RIGHT] > $this->m_maxY[GRAPH_YAXIS_SIDE_RIGHT]){
			if($this->graphOrientation[GRAPH_YAXIS_SIDE_RIGHT] == '-'){
				$this->m_minY[GRAPH_YAXIS_SIDE_RIGHT] = 0.2 * $this->m_maxY[GRAPH_YAXIS_SIDE_RIGHT];
			}
			else $this->m_minY[GRAPH_YAXIS_SIDE_RIGHT] = 0;
		}
//*/
		$this->calcMinMaxInterval();

		$this->updateShifts();
		$this->calcTriggers();
		$this->calcZero();
		$this->calcPercentile();

		$this->fullSizeX = $this->sizeX+$this->shiftXleft+$this->shiftXright+1;
		$this->fullSizeY = $this->sizeY+$this->shiftY+$this->legendOffsetY;

		if($this->drawLegend){
			$trCount = $this->m_showTriggers?count($this->triggers):0;
			$this->fullSizeY += 14 * ($this->num+1+(($this->sizeY < 120)?0:count($this->triggers))) + 8;
		}


		foreach($this->percentile as $side => $percentile){
			if(($percentile['percent']>0) && $percentile['value']){
				$this->fullSizeY += 14;
			}
		}

		if(function_exists('imagecolorexactalpha') && function_exists('imagecreatetruecolor') && @imagecreatetruecolor(1,1))
			$this->im = imagecreatetruecolor($this->fullSizeX,$this->fullSizeY);
		else
			$this->im = imagecreate($this->fullSizeX,$this->fullSizeY);


		$this->initColors();
		$this->drawRectangle();
		$this->drawHeader();

		if($this->num==0){
//				$this->noDataFound();
		}

		$this->drawWorkPeriod();
//*/ grid
		$this->drawTimeGrid();
		$this->drawVerticalGrid();
		$this->drawXYAxisScale($this->graphtheme['gridbordercolor']);
//-----*/

		$maxX = $this->sizeX;


// For each metric
		for($item = 0; $item < $this->num; $item++){
			$minY = $this->m_minY[$this->items[$item]['axisside']];
			$maxY = $this->m_maxY[$this->items[$item]['axisside']];

			$data = &$this->data[$this->items[$item]['itemid']][$this->items[$item]['calc_type']];

			if(!isset($data))	continue;

			if($this->items[$item]['calc_type'] == GRAPH_ITEM_AGGREGATED){
				$drawtype	= GRAPH_ITEM_DRAWTYPE_LINE;

				$max_color	= $this->getColor('HistoryMax');
				$avg_color	= $this->getColor('HistoryAvg');
				$min_color	= $this->getColor('HistoryMin');
				$minmax_color	= $this->getColor('HistoryMinMax');

				$calc_fnc	= CALC_FNC_ALL;
			}
			else if($this->type == GRAPH_TYPE_STACKED){
				$drawtype	= $this->items[$item]['drawtype'];

				$max_color	= $this->getColor('ValueMax',GRAPH_STACKED_ALFA);
				$avg_color	= $this->getColor($this->items[$item]['color'],GRAPH_STACKED_ALFA);
				$min_color	= $this->getColor('ValueMin',GRAPH_STACKED_ALFA);
				$minmax_color	= $this->getColor('ValueMinMax',GRAPH_STACKED_ALFA);

				$calc_fnc = $this->items[$item]['calc_fnc'];
			}
			else{
				$drawtype	= $this->items[$item]['drawtype'];

				$max_color	= $this->getColor('ValueMax', GRAPH_STACKED_ALFA);
				$avg_color	= $this->getColor($this->items[$item]['color'], GRAPH_STACKED_ALFA);
				$min_color	= $this->getColor('ValueMin', GRAPH_STACKED_ALFA);
				$minmax_color	= $this->getColor('ValueMinMax', GRAPH_STACKED_ALFA);

				$calc_fnc = $this->items[$item]['calc_fnc'];
			}

// For each X
			$draw = true;
			$prevDraw = true;
			for($i = 1, $j = 0; $i < $maxX; $i++){  // new point
				if(($data['count'][$i] == 0) && ($i != ($maxX-1))) continue;

				$diff	= abs($data['clock'][$i] - $data['clock'][$j]);
				$cell	= ($this->to_time - $this->from_time)/$this->sizeX;
				$delay	= $this->items[$item]['delay'];

				if($cell > $delay)
					$draw = (boolean) ($diff < (ZBX_GRAPH_MAX_SKIP_CELL * $cell));
				else
					$draw = (boolean) ($diff < (ZBX_GRAPH_MAX_SKIP_DELAY * $delay));

				if(($draw == false) && ($this->items[$item]['calc_type'] == GRAPH_ITEM_AGGREGATED))
					$draw = $i - $j < 5;

				if($this->items[$item]['type'] == ITEM_TYPE_TRAPPER) $draw = true;

				if(!$draw && !$prevDraw){
					$draw = true;
					$valueDrawType = GRAPH_ITEM_DRAWTYPE_BOLD_DOT;
				}
				else{
					$valueDrawType = $drawtype;
					$prevDraw = $draw;
				}

				if($draw){
					$this->drawElement(
						$data,
						$i, $j,
						0, $this->sizeX,
						$minY, $maxY,
//						$drawtype,
						$valueDrawType,
						$max_color,
						$avg_color,
						$min_color,
						$minmax_color,
						$calc_fnc,
						$this->items[$item]['axisside']
					);
				}
//echo '\nDraw II \n'; printf('%0.4f',(getmicrotime()-$start_time));

				$j = $i;
			}
		}

		$this->drawLeftSide();
		$this->drawRightSide();

		if($this->drawLegend){
			$this->drawTriggers();
			$this->drawPercentile();

			$this->drawLegend();
		}

		$this->drawLogo();

		$end_time=getmicrotime();
		$str=sprintf('%0.2f',(getmicrotime()-$start_time));

// if we get chart from config by get method
		$datafrom = '';
		if(isset($this->dataFrom))
			$datafrom = 'Data from '.$this->dataFrom.'. ';

		imagestring($this->im, 0,$this->fullSizeX-210,$this->fullSizeY-12,$datafrom.'Generated in '.$str.' sec', $this->getColor('Gray'));

		unset($this->items, $this->data);

//debug info
//		show_messages();

		ImageOut($this->im);
	}
}

?>