function GRAPH(id, selid) {
    this.config = null;
    this.window = null;
    this.source = null;
    this.exporter = null;
    this.selector = document.getElementById(selid);
    this.frame = document.getElementById(id);
    
    this.start_xy = [ 0, 0 ];
    this.margins = { left: 0, top: 0, right: 0, bottom: 0 };
    this.crop_margins = { left: 0, top: 0, right: 0, bottom: 0 };
    this.allWidth = 10;
    this.allHeight = 10;
    
    this.height = 0;
    this.width = 0;
    
    if (this.frame) {
	this.img = this.frame.getElementsByTagName('img')[0];
    } else {
	this.frame = null;
    }
    
/*    
    Event.observe(id,'click',this.MouseStart);
    Event.observe(id,'MouseMove', this.MouseStart);
    Event.observe(id,'MouseDown', this.MouseStart);
*/

    this.crop = null;
    this.extraButtons = new Array();

    if (this.img) {
	Event.observe(this.img, 'load', this.Configure.bindAsEventListener(this));
    } else {
	if (this.frame) alert('The "img" element is not found within specified block "' + id + '"');
	else alert('GRAPH is not able to locate specified ("' + id + '") element');
    }

    this.LegendCloseBind = this.LegendClose.bindAsEventListener( this );
    this.LegendCloseHelperBind = eventCanceler.bindAsEventListener();

	// Handling no data cases
    if (!isIE()) {
	this.img.addEventListener("DOMMouseScroll", this.onLocalScroll(this), false);
    }
    this.img.onmousewheel =  this.onLocalScroll(this);
    this.img.ondblclick = this.onLocalDblClick(this);
    this.img.onload = function() { adei.SetSuccessStatus(translate("Done")); }
}

GRAPH.prototype.RegisterCropperButton = function(info) {
    info.onClick = this.onButtonClick(this, info);
    this.extraButtons.push(info);
}


GRAPH.prototype.AttachConfig = function(config) {
    this.config = config;
//    config.Register(this);
}

GRAPH.prototype.Clear = function() {
    if (this.crop) {
	this.crop.clear();
    }
}


GRAPH.prototype.onEndCrop = function (self) {
    return function( coords, dimensions ) {
	if (self.window) {
	    if ((dimensions.width < self.allWidth)&&(dimensions.height < self.allHeight)) {
		self.window.SetCustomWindow(0, 0, 0, 0);
    	    } else if (dimensions.height < self.allHeight) {
		var xmin = self.xmin + (self.xmax - self.xmin)*(coords.x1 - self.margins.left) / self.real_width;
		var xmax = self.xmin + (self.xmax - self.xmin)*(coords.x2 - self.margins.left) / self.real_width;
		self.window.SetCustomWindow(xmin, xmax, 0, 0);
	    } else if (dimensions.width < self.allWidth) {
		var ymin = self.ymax - (self.ymax - self.ymin)*(coords.y2 - self.margins.top) / self.real_height;
		var ymax = self.ymax - (self.ymax - self.ymin)*(coords.y1 - self.margins.top) / self.real_height;
		self.window.SetCustomWindow(0, 0, ymin, ymax);
	    } else {
		var ymin = self.ymax - (self.ymax - self.ymin)*(coords.y2 - self.margins.top) / self.real_height;
		var ymax = self.ymax - (self.ymax - self.ymin)*(coords.y1 - self.margins.top) / self.real_height;
		var xmin = self.xmin + (self.xmax - self.xmin)*(coords.x1 - self.margins.left) / self.real_width;
		var xmax = self.xmin + (self.xmax - self.xmin)*(coords.x2 - self.margins.left) / self.real_width;
		self.window.SetCustomWindow(xmin, xmax, ymin, ymax);
	    }
	}
    }
	    
}

GRAPH.prototype.onCancelCrop = function (self) {
    return function() {
	if (self.window) {
	    self.window.SetCustomWindow(0,0,0,0);
//	    self.window.SetCustomWindow(self.config.from, self.config.to, self.config.win_min, self.config.win_max);
	}
    }
}

GRAPH.prototype.onApply = function (self) {
    return function ( coords, dimensions ) {
	self.window.Apply();
	self.crop.clear();
    }
}

GRAPH.prototype.onSave = function (self) {
    return function ( coords, dimensions ) {
	if (self.exporter) 
	    self.exporter.Export(true);
	else
	    adeiReportError("Data Exporter is not registered", "GRAPH");
    }
}

GRAPH.prototype.onButtonClick = function(self, info) {
    return function (corrds, dimensions) {
	var from = self.config.sel_from;
	var to = self.config.sel_to;

	eval('info.object.' + info.callback + '(from, to)');
    }
}


GRAPH.prototype.ProcessMouseScroll = function ( delta, mouse_x, mouse_y ) {
	switch (adei.key) {
	    case 72:	// h
		if (this.source) {
		    if (delta > 0)
			this.config.history.Back();
		    else
			this.config.history.Forward();
		}
	    break;
	    case 83:	// s
		if (this.source) {
		    if (delta > 0)
			this.source.NextServer();
		    else
			this.source.PrevServer();
		}
	    break;
	    case 68:	// d
		if (this.source) {
		    if (delta > 0)
			this.source.NextDatabase();
		    else
			this.source.PrevDatabase();
		}
	    break;
	    case 71:	// g
		if (this.source) {
		    if (delta > 0)
			this.source.NextGroup();
		    else
			this.source.PrevGroup();
		}
	    break;
	    case 77:	// m
		if (this.source) {
		    if (delta > 0)
			this.source.NextMask();
		    else
			this.source.PrevMask();
		}
	    break;
	    case 73:	// i
		if (this.source) {
		    if (delta > 0)
			this.source.NextItem();
		    else
			this.source.PrevItem();
		}
	    break;
	    case 69:	// e
		alert('not supported');
		
		if (this.experiment) {
		    if (delta > 0)
			this.experiment.NextItem();
		    else
			this.experiment.PrevItem();
		}
	    break;
	    case 84:	// t
	    case 16:	// Shift
		// t - time move
		if (delta > 0)
		    this.window.MoveLeft();
		else if (delta < 0)
		    this.window.MoveRight();
	    break;
	    case 86:	// v
		if (delta > 0)
		    this.window.MoveDown();
		else if (delta < 0)
		    this.window.MoveUp();
	    break;
	    case 87:	// w
		if (delta > 0)
		    this.window.IncreaseWidth();
		else if (delta < 0)
		    this.window.DecreaseWidth();
		
	    break;
	    case 89:	// y
		var y = this.ymax - (this.ymax - this.ymin)*(mouse_y - this.margins.top) / this.real_height;

		if (delta > 0)
		    this.window.YZoomOut(y);
		else if (delta < 0)
		    this.window.YZoomIn(y);
		
	    break;
	    case 67:	// c
		if (delta > 0)
		    this.window.CenterZoomOut();
		else if (delta < 0)
		    this.window.CenterZoomIn();
		
	    break;
	    case 76:	// l
	    case 90:	// z
		x = adeiMathPreciseAdd(this.xmin, this.xsize*(mouse_x - this.margins.left) / this.real_width);

		if (delta > 0)
		    this.window.LocalZoomOut(x);
		else if (delta < 0)
		    this.window.LocalZoomIn(x);
		
	    break;
	    case false:
		if (mouse_y > (this.height - this.margins.bottom)) {
		    if (delta > 0)
			this.window.MoveLeft();
		    else if (delta < 0)
			this.window.MoveRight();
		} else if (mouse_x < this.margins.left) {
		    if (delta > 0)
			this.window.MoveDown();
		    else if (delta < 0)
			this.window.MoveUp();
		} else if ((mouse_y >= this.margins.top)&&(mouse_x <= (this.width - this.margins.right))) {
		    var x = adeiMathPreciseAdd(this.xmin, this.xsize*(mouse_x - this.margins.left) / this.real_width);
		    var y = this.ymax - (this.ymax - this.ymin)*(mouse_y - this.margins.top) / this.real_height;
		    
		    if (delta > 0)
			this.window.ZoomOut(x, y);
		    else if (delta < 0)
			this.window.ZoomIn(x, y);
		}
	    break;
/*	    default:
		alert(adei.key);*/
	}
}

GRAPH.prototype.onMouseScroll = function (self) {
    return function ( delta, point ) {
	self.ProcessMouseScroll(delta, point.x, point.y);
//	alert(adei.key);
//adeiMathPreciseAdd(self.xmin, self.xsize*(point.x - self.margins.left) / self.real_width),	
//	alert(mouse_x + "(" + self.real_width + ")");

    }
}

GRAPH.prototype.onLocalDblClick = function(self) {
    return function(ev) {
	self.window.ResetX();
    }
}

GRAPH.prototype.onLocalScroll = function(self) {
    return function(ev) {
	switch (adei.key) {
	    case 83:	// s
	    case 68:	// d
		var delta = domGetScrollEventDelta(ev);
		self.ProcessMouseScroll(delta,0,0);
	    break;
	    case 71:	// g
	    case 67:	// c
	    case 76:	// l
	    case false:
		self.window.CenterZoomOut();
		// zoom out
	    break;
	}
    }
}

GRAPH.prototype.onMouseMove = function (self) {
    return function ( point, dragging, resizing ) {
//	if (self.config.GetModule() != "graph") return;
    
	if ((!point)||((point.x < self.margins.left)||(point.x > (self.width - self.margins.right)))||
	    ((point.y < self.margins.top)||(point.y > (self.height - self.margins.bottom)))) {
		adei.ClearStatus(self.mousepos_status_id);
		return;
	}
    
	var x = adeiMathPreciseAdd(self.xmin, self.xsize*(point.x - self.margins.left) / self.real_width);
	var y = self.ymax - (self.ymax - self.ymin)*(point.y - self.margins.top) / self.real_height;

	
	self.mousepos_status_id = adei.ProposeStatus(translate('Mouse cursor is at time: ') + adeiDateReadableFormat(x, self.xsize) + ", Y: " + y, 0);
    }
}



GRAPH.prototype.LegendClose = function(self) {
    return function(ev) {
	if (self.legend) {
	    self.legend.Hide();
	    self.legend = null;
	}
	Event.stop(ev);
    }
}

GRAPH.prototype.ShowLegend = function (self, mouse_x, mouse_y) {
    return function(transport) {
	if (transport.responseText.charAt(0) == '{') {
	    var json = transport.responseText.evalJSON(false);
    	    if (json.error) {
		adeiReportError(json.error);
	    } else {
		var html = "";//"<h4>Legend</h4>""<div class=\"window_close\"></div><h4>Legend</h4>";
		if ((json.legend)&&(json.legend.length>0)) {
		    html+="<table><tr><th>ID</th><th>Name</th></tr>";
		    for (var i = 0; i < json.legend.length; i++) {
			var item = json.legend[i];
			html+="<tr><td>" + item.id + "</td><td>" + item.name + "</td></tr>";
		    }
		    html+="</table>";
		} else {
		    html+="<p>Nothing selected</p>";
		}
		
		if ((self.legend)&&(self.legend.CheckReusability())) {
		    self.legend.AlterContent(html);
		    adei.SetSuccessStatus("Legend is updated");
		} else {
		    var legend = new DIALOG(self.LegendClose(self), "Legend", html, "legend");
		    legend.Show(self.frame, self.legend, mouse_x, mouse_y);
		    self.legend = legend;
		    
		    adei.ClearStatus(self.legend_status_id);
		}
	    }
	} else {
	    // XML
	}
    }
}

GRAPH.prototype.onClick = function (self) {
    return function ( ev, point ) {
	if ((point.x < self.margins.left)||(point.x > (self.width - self.margins.right))) return;
	if ((point.y < self.margins.top)||(point.y > (self.height - self.margins.bottom))) return;
    
	var x = adeiMathPreciseAdd(self.xmin, self.xsize*(point.x - self.margins.left) / self.real_width);
	var y = self.ymax - (self.ymax - self.ymin)*(point.y - self.margins.top) / self.real_height;

	switch (adei.key) {
	 case 67:	// c
	    self.window.Center(x,y);
	    return;
	 case 90:	// z
	    self.window.DeepZoom(x);
	    return;
	}


//	alert(adeiMathPreciseAdd(self.xmin, self.xsize*(point.x - self.margins.left) / self.real_width));
	var params = {
	    xmin: self.xmin.toString(),
	    xmax: self.xmax.toString(),
	    ymin: self.ymin,
	    ymax: self.ymax,
	    x : x,
	    y : y
	};


//	alert(self.xmax + " - " + self.xmin + "(" + self.xsize + "): " + point.x + "\n");
	
	self.legend_status_id = adei.SetStatus(translate("Loading legend..."), 0);
	new Ajax.Request(adei.GetServiceURL("legend"), 
	{
	    method: 'post',
	    requestHeaders: {Accept: 'application/json'},
	    parameters: { props: self.config.GetJSON(params) },
	    onSuccess: self.ShowLegend(self, ev.clientX, ev.clientY),
	    onFailure: function() {
		alert('GetLegend request is failed');
	    }
	});
    
	//updater.
    }
}


GRAPH.prototype.onDblClick = function(self) {
    return function (ev, point) {
	if (point.x < self.margins.left) {
	    self.window.ResetY();
	} else if (point.y > (self.height - self.margins.bottom)) {
	    self.window.ResetX();
	}
    }
}

GRAPH.prototype.AttachWindow = function (window) {
    this.window = window;
    window.RegisterGraph(this);
}

GRAPH.prototype.AttachSource = function (source) {
    this.source = source;
}

GRAPH.prototype.AttachExporter = function (exporter) {
    this.exporter = exporter;
}

GRAPH.prototype.SetMargins = function (l,t,r,b) {
    this.margins = { left: l, top: t, right: r, bottom: b };
    this.crop_margins = { left: l, top: t, right: r, bottom: b };
    if (this.crop) this.crop.setMargins(l, t, r, b);
}

GRAPH.prototype.SetAllSizes = function (w,h) {
    this.allWidth = w;
    this.allHeight = h;
}

GRAPH.prototype.Prepare = function() {
	/* Fixing IE6. Diff is non-zero only in IE6, however certain
	CSS settings could bring it to zero even in IE6. For example:
	html, body { width: 100%; } */
    var diff = document.body.offsetWidth - windowGetWidth();
    if (diff > 0) {
	var new_size = this.frame.offsetWidth - diff;
	if (new_size > 0) {
	    this.config.SetupGeometry(new_size, this.frame.offsetHeight);
	    return;
	}
    }

    this.config.SetupGeometry(this.frame);
}

GRAPH.prototype.Configure = function() {
	    this.real_height = this.img.height - this.margins.top - this.margins.bottom;
	    this.real_width = this.img.width - this.margins.left - this.margins.right;
	    
	    if (this.xsize) {
		var new_width = this.img.width;
		var new_height = this.img.height;
		
		if (this.crop) {
		    
		    if ((new_width != this.width)||(new_height != this.height)) {
			this.width = new_width;
			this.height = new_height;
			this.crop.setParams();	// DS. this action slows evertything down after several steps
		    }
	        } else {
		    var tooltip;
		    if (this.exporter) 
			tooltip = " (" + this.exporter.GetTooltip() + ")";
		    else
			tooltip = "";

		    this.crop = new Cropper.Img("graph_image", {
    			onEndCrop:  this.onEndCrop(this),
			onCancelCrop: this.onCancelCrop(this),
			onClick: this.onClick(this),
			onDblClick: this.onDblClick(this),
			onDblSelClick: this.onApply(this),
			onMouseScroll: this.onMouseScroll(this),
			onMouseMove: this.onMouseMove(this),
			onApplyClick: this.onApply(this),
			onSaveClick: this.onSave(this),
			extraButtons: this.extraButtons,
			margins: this.crop_margins,
			allWidth: this.allWidth,
			allHeight: this.allHeight,
			monitorImage: false,
			imageReady: true,
			tooltips: new Object({
			    'apply': translate('Zoom to the Selection'),
			    'save': translate('Export Selected Data') + tooltip
			})
		    });
		    
		    this.width = new_width;
		    this.height = new_height;
		}
	    }


}


GRAPH.prototype.Update = function(json, forced) {
    if (json.draw) {
	if (json.nodata) {
	    this.img.src = adei.GetServiceURL("getimage", "id=" + json.image);
	    
	    if (this.crop) {
		this.crop.remove();
		this.crop = null;
	    }
	    
		// indictating it is not valid window
	    this.xsize = 0;
	} else if (this.img) {
	    var doit = 1;

	    if (this.crop) {
		var crop = this.crop;
		if ((crop.selected)||(crop.dragging)||(crop.resizing)||(crop.calcW()>this.allWidth)||(crop.calcH()>this.allHeight)) {
		    if (forced) this.crop.clear();
		    else {
			adei.SetStatus(translate("Canceled because of selection"));
			doit = 0;
		    }
		}
	    }

	    if (doit) {
		adei.SetStatus(translate("Loading Image..."));
	        this.img.src = adei.GetServiceURL("getimage", "id=" + json.image);

		this.xmin = parseFloat(json.xmin);
		this.xmax = parseFloat(json.xmax);
		this.ymin = parseFloat(json.ymin);
		this.ymax = parseFloat(json.ymax);
		this.imin = parseFloat(json.imin);
		this.imax = parseFloat(json.imax);
		this.prec = parseFloat(json.precision);
		this.yzoom = json.yzoom;
		
		if (json.margins) {
		    this.SetMargins(json.margins[0], json.margins[1], json.margins[2], json.margins[3]);
		}
		
		this.xsize = parseFloat(adeiMathPreciseSubstract(this.xmax, this.xmin));
		this.ysize = this.ymax - this.ymin;

//		alert(this.xsize + " - " + (this.xmax - this.xmin));
		
//		alert(json.xmin + " - " + this.xmin);

//	    x : self.xmin + (self.xmax - self.xmin)*(point.x - self.margins.left) / self.real_width,
//	    y : self.ymax - (self.ymax - self.ymin)*(point.y - self.margins.top) / self.real_height

		this.window.NewGraph(this.xmin, this.xmax);
	    }
	}
    }
}

GRAPH.prototype.SetExportTooltip = function () {
    var tooltip = this.exporter.GetTooltip();
    if ((this.crop)&&(tooltip)) {
	this.crop.setTooltip(new Object({'save': translate("Export Selected Data (%s)", tooltip)}));
    }
}

GRAPH.prototype.NotifyExportSettings = function() {
    this.SetExportTooltip();
}

//GRAPH.prototype.NotificationExport


GRAPH.prototype.AdjustGeometry = function(width, height, hend, vend) {
/*
    This was rougher approach
    domAdjustGeometry(this.frame, width, height - (this.selector?this.selector.offsetHeight:0), true);
*/
    
	// otherwise we are probably not on display
    if (this.frame.offsetParent) {
	var mboffset = domGetNodeOffset(this.frame);
	var w = hend - mboffset[0];
	var h = vend - mboffset[1] - 1;
	domAdjustGeometry(this.frame, w, h, true);
    }
}


GRAPH.prototype.GetPrecision = function(min) {
    var prec = this.prec;
    if (min > prec) prec = min;
    
    return (this.xsize * prec) / this.real_width;
}

/*
GRAPH.prototype.MouseSelection = function(xy) {
    alert(this.start_xy[0] - xy[0]);
}

GRAPH.prototype.MouseStart = function(evt) {
    var xy = domGetMouseOffset(evt);
    this.start_xy = xy;
    return false;
}

GRAPH.prototype.MouseDone = function(evt) {
    var xy = domGetMouseOffset(evt);
    this.MouseSelection(xy);
}

GRAPH.prototype.MouseVisualize = function(evt) {
    var xy = domGetMouseOffset(evt);
    return false;
}
*/