SQ4.StockConst = {
	timeAxisHelpData: [{ format: 'yyyy', size: SQ4.TimeConsts.year, step: 'year' },
	{ format: 'mm.yyyy', size: SQ4.TimeConsts.month, step: 'month' },
	{ format: 'dd.mm', size: SQ4.TimeConsts.day, step: 'day' },
	{ format: 'dd.mm HH:MM', size: SQ4.TimeConsts.day / 2, step: 'halfDay' },
	{ format: 'dd.mm HH:MM', size: SQ4.TimeConsts.day / 3, step: 'thirdDay' },
	{ format: 'dd.mm HH:MM', size: SQ4.TimeConsts.day / 4, step: 'quaterDay' },
	{ format: 'dd.mm HH:MM', size: SQ4.TimeConsts.hour, step: 'hour' },
	{ format: 'dd.mm HH:MM', size: SQ4.TimeConsts.minute, step: 'minute' },
	{ format: 'MM:ss', size: SQ4.TimeConsts.sec, step: 'sec' },
	{ format: 'ss.L', size: SQ4.TimeConsts.milisec, step: 'milisec' }],

	DEFAULT_CONFIG: {
		logLevel: 'error',   //loging level:error, info, debug, trace
		design: {
			backgroundColor_dark: '#3b3b3b',
			backgroundColor: '#fff',  //background color of chart
			padding: { top: 10, bottom: 10, right: 10, left: 10 },  // padding of chart
			borderColor: '#aaa',
			borderColor_dark: '#888',
			borderWidth: 1,
			emptyTextColor: 'black',
			emptyTextColor_dark: 'white',
			emptyTextFont: '12px Arial'
		},
		chart: {  //configuration of each type of chart
			bar: {	//bar chart
				space: 2,		//minimal space between bars
				maxWidth: 25
			},
			point: {	//point chart
				radius: 2	// radius of points
			},
			line: {	//line chart
				thickness: 1,	//default thickness of line
				pattern: [],
				fillBelow: false	//flag if area below the line should be filled - NOT IMPLEMENTED YET
			},
			candle: {
				upTradeColor: 'green',
				downTradeColor: 'red',
				shadowColor: 'black',
				space: 2,
				maxWidth: 1000,
				indicatorGrowFactor: 0.2,
				candleGrowFactor: 0.1
			},
			preview: {
				color: '#daf5ff',
				color_dark: '#9ab5bf',
				thickness: 1,
				space: 5,
				background: '#fff',
				background_dark: '#3b3b3b',
				maxHeight: 60,
				separatorColor: '#d3d3d3',
				hintColor: '#aaa',
				hintColor_dark: '#fff',
				hintFont: '11px Arial'
			},
			subchartMaxHeight: 90,
			mainchartMinHeight: 50 //in percent
		},
		axis: {	// chart's axises
			color_dark: '#eee',
			color: '#222',	// color for lines
			font: '12px Arial',	// font for captions
			gridColor: '#777777',	// grid's color (if visible)
			gridColor_dark: '#ccc',
			gridLinePattern: [1, 1],
			gridLineThickness: 1,
			gridVisible: true,
			axisOverLine: 3,			// overlap of dots over the axis
			minimumStepSizeX: 10,	// minimal space between 2 points on X axis
			minimumStepSizeY: 27,		// minimal space between 2 points on Y axis
			shortCaptions: false,
			yAxisSpaceLeft: 5,
			yAxisSpaceRight: 5,
			crossColor: 'black',
			crossColor_dark: '#aaa',
			crossWidth: 1,
			hintFontSize: 12,
			hintBackgroundColor: 'black',
			hintBackgroundColor_dark: 'white',
			hintColor: 'white',
			hintColor_dark: 'black',
			hintPadding: '1px 5px'
		},
		chartCaptions: {
			color: '#222',
			color_dark: '#eee',
			font: '12px Arial',
			padding: { left: 5, top: 5 }
		},
		indicator: {
			precision:5,
			levelLineColor: '#eee',
			levelLineThickness: 1,
			levelLinePattern: [3, 3]
		},
		markers: {
			openArrowWidth:15,
			openSquareColor: '#0000ffee',
			openTextColor: 'yellow',
			closeSquareProfitColor: '#007700dd',
			closeTextProfitColor: 'white',
			closeSquareLossColor: '#ff0000dd',
			closeTextLossColor: 'white',
			closeSquareZeroColor: '#999999dd',
			closeTextZeroColor: 'white',			
			lineLongProfitColor: 'green',
			lineLongLossColor: 'red',
			lineShortProfitColor: 'green',
			lineShortLossColor: 'red',
			buyText: 'Buy at {value}',
			sellText: 'Sell at {value}',
			openCloseTextTicket:'Ticket:{ticket}',
			closeTextValue:'Close at {value}',
			closeTextPL:'PL: {plValue}',
			squareCaptionSpace: 2,
			font: '12px Arial',
			highlightedLineWidth:3,
			highlightArrowColor: 'orange',
			zoomPart: 3,
			squarePadding: 3,
			linePattern: [4, 4],
			triangleColor: '#555',
			triangleFillColor: 'yellow',
			showCloseTicket:true,
			showOpenCloseValue:true,
			showClosePL:true,
			showInPreview:true,
			previewColor:'#ff000088',
			previewHeight:3,
			selectedColor:'#11111111'
		},
		levels: {
			labelAxisSpace: 2,
			alpha: '99',
			padding: 2
		},
		zoom: {
			external: false,
			minimum: 10,
			maximum: 400,
			step: 2,
			padding: 2,
			minScrollerWidth: 20,
			scrollerSideLeftCss: 'opacity:0.8;background:#eee;',
			scrollerSideRightCss: 'opacity:0.8;background:#eee;',
			scrollerContentCss: 'opacity:0.3; background:#8092b8',
			rangeCaptionCss: 'font-size:11px;color:#aaa',
			rangePrefix: 'Displaying range: ',
			screensPerOneWheel: 0.1
		},
		data: {
			cache: true
		},
		precision: 5
	}
}

SQ4.StockTooltipManager = function () {
	var me = this;
	this.cache = {};
	this.regions = {};

	this.clear = function () {
		this.cache = {};
		this.regions = {};
	}

	this.addRegion = function (x1, x2, xCaption, value, kind, context) {
		var regionGroup = this.regions[context.group];
		if (!regionGroup) {
			var regionGroup = [];
			this.regions[context.group] = regionGroup;
		}
		regionGroup.push({ value: value, title: context.title, kind: kind, x1: x1, x2: x2, line: context.indicatorLine, id: context.id, xCaption: xCaption });
	}

	this.get = function (x, xValuesForGroup) {
		var result = { groups: [] };

		for (var groupId in me.regions) {
			var iRegion = me.regions[groupId];
			if (xValuesForGroup && xValuesForGroup != groupId) {
				continue;
			}

			var tmpGroup;
			iRegion.forEach(function (region) {
				if (region.x1 <= x && x <= region.x2) {
					var value = { value: region.value, line: region.line, id: region.id, subtitle: region.title, kind: region.kind };
					var found = false;
					result.groups.forEach(function (group) {
						if (group.group == groupId) {
							found = true;
							group.items.push(value);
						}
					});

					if (!found) {
						tmpGroup = { group: groupId, title: region.title, dateTime: region.xCaption, items: [value] };
						result.groups.push(tmpGroup);
					}else if (tmpGroup && region.kind=='chart'){
						tmpGroup.title = region.title;
					}
				}
			});
		}

		return result;
	}
}

SQ4.StockCrossDrawer = function (config, wrapper) {
	var me = this;
	this.config = config;

	this.horizontalLine = null;
	this.verticalLine = null;
	this.XCaption = null;
	this.YCaption = null;

	this.visible = true;
	this.themeLight = true;

	this.getAttributeForTheme = function (config, attrib) {
		var themedAttrib = me.themeLight === true ? attrib : attrib + "_dark";
		if (typeof config[themedAttrib] != 'undefined') {
			return config[themedAttrib];
		}

		return config[attrib];
	}

	function init() {
		var axisConfig = me.config.axis;

		me.horizontalLine = document.createElement("div");
		me.horizontalLine.style = "height:1px;background:" + axisConfig.crossColor + ";position:absolute";

		me.verticalLine = document.createElement("div");
		me.verticalLine.style = "width:1px;background:" + axisConfig.crossColor + ";position:absolute";

		me.XCaption = document.createElement("div");
		me.XCaption.style = "font-size:" + axisConfig.hintFontSize + "px;color:" + axisConfig.hintColor + ";background:" + axisConfig.hintBackgroundColor + ";position:absolute;padding:" + axisConfig.hintPadding;

		me.YCaption = document.createElement("div");
		me.YCaption.style = "font-size:" + axisConfig.hintFontSize + "px;color:" + axisConfig.hintColor + ";background:" + axisConfig.hintBackgroundColor + ";position:absolute;padding:" + axisConfig.hintPadding;

		wrapper.appendChild(me.horizontalLine);
		wrapper.appendChild(me.verticalLine);
		wrapper.appendChild(me.XCaption);
		wrapper.appendChild(me.YCaption);

		me.setVisible(false);
	}

	this.setBorders = function (chartLeft, chartRight, chartBottom) {
		this.chartLeft = chartLeft;
		this.chartRight = chartRight;
		this.chartBottom = chartBottom;

		me.horizontalLine.style.left = chartLeft + "px";
		me.horizontalLine.style.width = (chartRight - chartLeft) + "px";

		me.verticalLine.style.top = this.config.design.padding.top + "px";
		me.verticalLine.style.height = (chartBottom - this.config.design.padding.top) + "px";

		me.XCaption.style.top = chartBottom + 'px';
		me.YCaption.style.left = chartRight + 'px';
	}

	this.draw = function (x, y, chartLeft, chartRight, chartBottom, textX, textY) {
		if (x < this.chartLeft || x > this.chartRight || y > this.chartBottom || y < this.config.design.padding.top) {
			this.setVisible(false);
			return;
		}

		this.setVisible(true);

		me.horizontalLine.style.top = y + "px";
		me.verticalLine.style.left = x + "px";

		me.XCaption.innerHTML = textX;
		var width = me.XCaption.offsetWidth;
		x -= width / 2;
		me.XCaption.style.left = x + 'px';

		me.YCaption.innerHTML = textY;
		var height = me.YCaption.offsetHeight;
		y -= height / 2;
		me.YCaption.style.top = y + 'px';

		var hintColor = this.getAttributeForTheme(me.config.axis, 'hintColor');
		var hintBackgroundColor = this.getAttributeForTheme(me.config.axis, 'hintBackgroundColor');
		var crossColor = this.getAttributeForTheme(me.config.axis, 'crossColor');

		me.XCaption.style.color = hintColor;
		me.XCaption.style.background = hintBackgroundColor;

		me.YCaption.style.color = hintColor;
		me.YCaption.style.background = hintBackgroundColor;

		me.horizontalLine.style.background = crossColor;
		me.verticalLine.style.background = crossColor;
	}

	this.setVisible = function (visible) {
		if (this.visible == visible) {
			return;
		}

		this.visible = visible;

		me.horizontalLine.style.display = visible ? "" : "none";
		me.verticalLine.style.display = visible ? "" : "none";
		me.XCaption.style.display = visible ? "" : "none";
		me.YCaption.style.display = visible ? "" : "none";
	}

	init();
}

SQ4.StockChartHelper = function (canvasHelper, config) {
	var me = this;
	this.config = config;
	this.canvasHelper = canvasHelper;
	this.chartCanvasSize = null;
	this.factorX = 1;
	this.data = null;
	this.chartLeft = 0;
	this.chartRight = 0;
	this.chartBottom = 0;
	this.previewTop = 0;
	this.chartLeftRelativeToData = 0;
	this.rightAxisWidth = 0;
	this.domainAxisRenderer = new SQ4.AxisRenderer.DateRenderer(false);
	this.YAxisRenderer = new SQ4.AxisRenderer.NumberRenderer(false);
	this.domainTimeString = false;
	this.tooltipManager = new SQ4.StockTooltipManager();
	this.drawInfo = {};
	this.minMaxX;
	this.allowedIndicators = null;
	this.allowedTradeMarkers = null;
	this.drawingContext = {};
	
	this.highlightedTradeId = null;
	this.highlightedTradeIndex = null;
	this.highlightedTradeDetail = null;
	
	this.candleWidth = null;
	this.candleWidthHalf = null;
	this.customSettings = {};

	this.themeLight = true;

	this.getAttributeForTheme = function (config, attrib) {
		var themedAttrib = me.themeLight === true ? attrib : attrib + "_dark";
		if (typeof config[themedAttrib] != 'undefined') {
			return config[themedAttrib];
		}

		return config[attrib];
	}

	this.prepareCustomSettings = function (id) {
		var customSettings = me.customSettings[id];
		if (!customSettings) {
			customSettings = {};
			me.customSettings[id] = customSettings;
		}

		return customSettings;
	}

	this.addCustomLevel = function (chartId, level) {
		var customSettings = me.prepareCustomSettings(chartId);

		var levels = customSettings.levels;
		if (!levels) {
			levels = [];
			customSettings.levels = levels;
		}
		levels.push(level);
	}

	this.setCustomAttribute = function (id, index, attribute, value) {
		var customSettings = me.prepareCustomSettings(id);
		if (index != null) {
			var ds = customSettings.datasets;
			if (!ds) {
				ds = {};
				customSettings.datasets = ds;
			}
			var dsSettings = ds[index];
			if (!dsSettings) {
				dsSettings = {};
				ds[index] = dsSettings;
			}
			dsSettings[attribute] = value;
		} else {
			customSettings[attribute] = value;
		}
	}

	this.getValue = function (id, source, index, attribute, defaultValue) {
		var customSettings = me.customSettings[id];
		var value;
		if (customSettings) {
			if (index == null) {
				value = customSettings[attribute];
			} else {
				var ds = customSettings.datasets;
				if (ds && ds[index]) {
					var dsSettings = ds[index];
					value = dsSettings[attribute];
				}
			}
		}

		if (typeof value !== 'undefined') {
			return value;
		}
		value = source[attribute];
		if (typeof value === 'undefined') {
			value = defaultValue;
		}

		return value;
	}

	this.clearCustomSettings = function () {
		this.customSettings = {};
	}

	this.clear = function () {
		var color = me.getAttributeForTheme(me.config.design, 'backgroundColor');
		this.canvasHelper.clear(color);
	}

	this.getMaxWidthFromNumbers = function (chart, axisData, max, horizontal) {
		var maxWidth = 0;
		if (axisData == null) {
			return maxWidth;
		}

		axisData.current = axisData.start;

		var renderer = horizontal ? me.domainAxisRenderer : me.YAxisRenderer;

		while (axisData.current < max) {
			var textForRender = renderer.getCaption(axisData.current, me.config.axis.shortCaptions, axisData.dformat);
			var width = me.canvasHelper.getTextWidth(textForRender, me.config.axis.font);
			if (width > maxWidth) {
				maxWidth = width;
			}

			renderer.getNextValue(axisData);
		}

		axisData.current = axisData.start
		return maxWidth;
	}

	this.countAxises = function () {
		var maxWidth = 0;
		for (var chartId in me.drawInfo) {
			if (chartId == 'preview') {
				continue;
			}

			var drawInfo = me.drawInfo[chartId];
			var dsHeight = drawInfo.computedHeight;

			var setPrecision = typeof drawInfo.precision !== 'undefined';
			var format = setPrecision ? drawInfo.precision : me.config.precision;
			var lMinMax = {
				min: drawInfo.minMaxY.min.toFixed(format) * 1,
				max: drawInfo.minMaxY.max.toFixed(format) * 1
			};

			var axisData = me.YAxisRenderer.getStartStep(dsHeight, lMinMax, me.config.axis.minimumStepSizeY, drawInfo.precision);
			drawInfo.axisesY = axisData;
			if (!setPrecision) {
				drawInfo.precision = drawInfo.axisesY.dformat;
			}

			var axisWidth = me.getMaxWidthFromNumbers(drawInfo.chart, axisData, drawInfo.minMaxY.max, false);
			if (maxWidth < axisWidth) {
				maxWidth = axisWidth;
			}
		}

		me.rightAxisWidth = maxWidth + me.config.axis.yAxisSpaceLeft + me.config.axis.yAxisSpaceRight;
		this.chartCanvasSize.width -= Math.round(me.rightAxisWidth);
	};

	this.drawAxis = function (ds, location, minMaxY, axisesY) {
		SQ4.Utils.log('Drawing axis: ' + location.caption + ' width:' + location.width + ', height:' + location.height + ' left:' + location.x + ', top:' + location.y, SQ4.Consts.LOG_DEBUG);

		var color = me.getAttributeForTheme(me.config.axis, 'color');
		//left vertical axis
		//me.canvasHelper.drawLine(me.realChartLeft, location.y, me.realChartLeft, location.y+location.height, color);

		//horizontal axis 
		me.canvasHelper.drawLine(me.realChartLeft, location.y + location.height, me.chartRight, location.y + location.height, color);

		this.drawAxisYSteps(ds, location, minMaxY, axisesY);
	};

	this.drawAxisX = function (top) {
		var index = 0;

		me.domainAxisRenderer.getStartStep(me.chartCanvasSize.width, me.minMaxX, me.config.axis.minimumStepSizeX, SQ4.StockConst.timeAxisHelpData);

		var textY = 100;
		var textHeight = me.canvasHelper.getTextHeight(me.config.axis.font);
		var textHeight2 = textHeight / 2;

		var nextAllowedX = null;
		var gridColor = me.getAttributeForTheme(me.config.axis, 'gridColor');
		var color = me.getAttributeForTheme(me.config.axis, 'color');
		me.xVals.forEach(function (xVal) {
			var date = new Date(xVal);
			var x = me.chartLeft + me.factorX * (index + 1);
			if (!me.isOutOfScreen(x)) {
				if (me.isForex()) {
					x -= me.candleWidthHalf;
				} else {
					x += me.candleWidthHalf;
				}

				if (nextAllowedX == null || x >= nextAllowedX) {
					if (me.config.axis.gridVisible === true) {
						me.canvasHelper.drawLine(x, me.config.design.padding.top, x, top, gridColor, { width: me.config.axis.gridLineThickness, lineDash: me.config.axis.gridLinePattern });
					}

					me.canvasHelper.drawLine(x, top - me.config.axis.axisOverLine, x, top, gridColor);

					var textForRender = me.domainAxisRenderer.getCaption(xVal);
					var textWidth = me.canvasHelper.getTextWidth(textForRender, me.config.axis.font);
					var halfText = textWidth / 2;
					x -= halfText;
					if (x < me.chartLeft) {
						x = me.chartLeft;
					}

					nextAllowedX = x + textWidth + halfText + me.config.axis.minimumStepSizeX;
					var textParts = textForRender.split('\n');

					var textY = top + textHeight2;
					if (!me.isOutOfScreen(x)) {
						textParts.forEach(function (text) {
							me.canvasHelper.drawText(text, color, me.config.axis.font, x, textY);
							textY += textHeight;
						});
					}
				}
			}

			index++;
		});
	}

	this.drawAxisYSteps = function (ds, location, minMax, axises, drawValues) {
		if (minMax.min == null || minMax.max == null || axises.start == null) {
			return;
		}

		var size = location.height;
		var start = axises.start;

		SQ4.Utils.log('Drawing axis labels. Start:' + start + ', step:' + axises.step + ', min:' + minMax.min + ', max:' + minMax.max, SQ4.Consts.LOG_DEBUG);

		var dataSize = Math.abs(minMax.max - minMax.min);
		var factorY = size / dataSize;

		var textHeight = me.canvasHelper.getTextHeight(me.config.axis.font);
		var textHeightQuarter = textHeight / 4;
		axises.current = start;
		var lastY = -1;
		var lastYTxt = '';

		var gridColor = me.getAttributeForTheme(me.config.axis, 'gridColor');
		var color = me.getAttributeForTheme(me.config.axis, 'color');
		while (axises.current <= minMax.max) {
			var x1, x2, y1, y2;
			var textY;


			var textForRender = me.YAxisRenderer.getCaption(axises.current, me.config.axis.shortCaptions, axises.dformat);
			//var textForRender = me.YAxisRenderer.getCaption(axises.current, me.config.axis.shortCaptions, me.getNumberPrecision());

			x1 = me.realChartLeft;
			x2 = location.x + location.width;

			y1 = location.y + location.height - factorY * (axises.current - minMax.min);
			y2 = y1;

			textY = y1 + textHeightQuarter;

			if (textY - textHeightQuarter < location.y) {
				textY = location.y + textHeightQuarter;
			}

			if (textY > location.y + location.height) {
				textY = location.y + location.height;
			}

			if (textForRender != lastYTxt && Math.abs(lastY - textY) >= me.config.axis.minimumStepSizeY) {
				if (me.config.axis.gridVisible === true) {
					me.canvasHelper.drawLine(x1, y1, x2, y2, gridColor, { width: me.config.axis.gridLineThickness, lineDash: me.config.axis.gridLinePattern });
				}
				me.canvasHelper.drawLine(me.chartRight - me.config.axis.axisOverLine, y1, me.chartRight + me.config.axis.axisOverLine, y2, gridColor);

				me.canvasHelper.drawText(textForRender, color, me.config.axis.font, me.chartRight + me.config.axis.yAxisSpaceLeft, textY);

				lastY = textY;
				lastYTxt = textForRender;
			}

			me.YAxisRenderer.getNextValue(axises);
		}
	}

	this.drawAxises = function () {
		var padding = me.config.design.padding;
		var top = padding.top;

		for (var chartId in me.drawInfo) {
			if (chartId == 'preview') {
				continue;
			}

			var chartInfo = me.drawInfo[chartId];
			var dsHeight = chartInfo.computedHeight;
			var location = { x: me.chartLeft, y: top, width: me.chartRight - me.chartLeft, height: dsHeight };

			this.drawAxis(chartInfo.chart, location, chartInfo.minMaxY, chartInfo.axisesY, chartInfo.last);
			top += dsHeight + me.getSizeBetweenCharts();
		}

		//draw X axis + horizontal grid lines
		this.drawAxisX(top);
	};

	this.drawLevel = function (drawInfo, limits, level) {
		var minMax = drawInfo.minMaxY;

		var isObject = SQ4.Utils.isObject(level);
		var color, width, value, pattern;

		if (isObject === true) {
			color = level.color;
			value = level.value;
			width = level.width;
			pattern = level.pattern;
		} else {
			value = level;
		}

		if (SQ4.Utils.isNullOrUndefined(value)) {
			return;
		}

		if (value < minMax.min || value > minMax.max) {
			return;
		}

		if (SQ4.Utils.isNullOrUndefined(color)) {
			color = me.getAttributeForTheme(me.config.indicator, 'levelLineColor');
		}

		if (SQ4.Utils.isNullOrUndefined(width)) {
			width = me.config.indicator.levelLineThickness;
		}

		if (SQ4.Utils.isNullOrUndefined(pattern)) {
			pattern = me.config.indicator.levelLinePattern;
		}

		var y = limits.y2 - drawInfo.factorY * (value - minMax.min);
		me.canvasHelper.drawLine(limits.x1, y, limits.x2, y, color, { width: width, lineDash: pattern });

		var textForRender = this.YAxisRenderer.getCaption(value, me.config.axis.shortCaptions, drawInfo.axisesY.dformat);
		//var textForRender = this.YAxisRenderer.getCaption(value, me.config.axis.shortCaptions,me.getNumberPrecision());
		var textHeight = me.canvasHelper.getTextHeight(me.config.markers.font);
		var textWidth = me.canvasHelper.getTextWidth(textForRender, me.config.markers.font);

		var colorHex = SQ4.Utils.normalizeHexColor(color);
		var options = { stroke: false, fill: true, fillColor: colorHex + me.config.levels.alpha, radius: 5 };
		var padding = me.config.levels.padding;

		var captionX = limits.x2 + me.config.levels.labelAxisSpace;
		var captionY = y - textHeight / 2;
		me.canvasHelper.drawRoundedRectangle(captionX, captionY, textWidth + 2 * padding, textHeight, options);

		var textColor = SQ4.Utils.getSuitableOpositeColor(color);
		me.canvasHelper.drawText(textForRender, textColor, me.config.axis.font, captionX + padding, captionY + textHeight - padding - 2);
	}

	this.selectTradeAtX = function(x, y){
		if (x > me.chartRight || x < me.chartLeft) {
			return;
		}
		
		var group = me.getGroup(y, true);
		if (!group) {
			return;
		}
		
		var trades = me.drawInfo[group].trades;
		for (var i = 0; i < trades.length; i++) {
			var region = trades[i];
			
			if (region.from<=x && x<=region.to){
				me.highlightedTradeId = region.id;
				me.highlightedTradeIndex = region.index;
				me.refresh(true);
				return me.highlightedTradeDetail;
			}
		}
		
		me.highlightedTradeId = null;
		me.highlightedTradeIndex = null;
		me.refresh(true);
	}

	this.drawData = function () {
		function drawDs(drawInfo, xVals, limits, ds) {
			var chartHeight = drawInfo.computedHeight;

			me.drawingContext.candleMap = [];
			drawTrademarkerHighlight(ds, limits, drawInfo.minMaxY);
			var mainFactorY = me.drawDataSet(drawInfo, xVals, ds, limits, drawInfo.minMaxY);
			var barWidth = me.getBarWidth(xVals, ds.yVals) / 2;
			me.drawTradeMarkers(ds, mainFactorY, limits, drawInfo.minMaxY, barWidth);
			me.drawingContext.candleMap = null;

			return xVals && xVals.length > 0;
		}
		
		function drawTrademarkerHighlight(chart, limits, minMaxY){
			if (me.highlightedTradeId==null){
				return;
			}
			
			var tradeMarkers = chart.tradeMarkers;
			if (!tradeMarkers || !me.isTradeMarkersAllowed(chart.id)) {
				return;
			}
			
			var dataHeight = Math.abs(minMaxY.max - minMaxY.min);
			var height = limits.y2 - limits.y1;
			var factorY = height / dataHeight;
			
			tradeMarkers.forEach(function (tm) {
				if (me.highlightedTradeId!=tm.id){
					return;
				}
				
				me.highlightedTradeDetail = tm;
				me.highlightedTradeIndex = tm.index;
				
				var openX = me.getX(tm.openTimestamp);
				var openY = tm.openValue;

				var closeX = me.getX(tm.closeTimestamp);
				var closeY = tm.closeValue;

				if (!openX && !closeX) {
					openX = -1;
					closeX = me.chartRight + 1;
				} else if (!openX) {
					openX = -1;
				} else if (!closeX) {
					closeX = me.chartRight + 1;
				}

				var long = tm.long;

				var _openX = limits.x1 + openX;
				var _closeX = limits.x1 + closeX;
				
				var color = me.config.markers.selectedColor;
				me.canvasHelper.drawFilledRectangle(_openX, limits.y1, _closeX-_openX, limits.y2-limits.y1, color);
			});
		}

		function drawIndicators(drawInfo, xVals, limits, indicator, drawCaption) {
			var drawn = false;
			var index = 0;
			var step = me.canvasHelper.getTextHeight(titleConf.font);

			var moreLines = indicator.datasets.length > 1;
			indicator.datasets.forEach(function (ds) {
				me.drawingContext.id = index;
				me.drawingContext.indicatorLine = ds.line;
				me.drawingContext.index = index;
				if (drawDs(drawInfo, xVals, limits, ds)) {
					drawn = true;
				}

				if (drawCaption === true) {
					var text = moreLines === true ? me.drawingContext.title + "." + ds.line : me.drawingContext.title;
					me.drawingContext.lastCaptionTop += step;

					if (me.drawingContext.lastCaptionTop <= limits.y2) {
						me.canvasHelper.drawText(text, ds.color, titleConf.font, me.realChartLeft + titleConf.padding.left, me.drawingContext.lastCaptionTop);
					}
				}

				index++;
			});

			if (indicator.levels && indicator.levels != null) {
				indicator.levels.forEach(function (level) {
					me.drawLevel(drawInfo, limits, level);
				});
			}

			var customSettings = me.customSettings[me.drawingContext.chartId];
			if (customSettings) {
				var levels = customSettings.levels;
				if (levels) {
					levels.forEach(function (level) {
						me.drawLevel(drawInfo, limits, level);
					});
				}
			}

			return drawn;
		}

		SQ4.Utils.log('Drawing data', SQ4.Consts.LOG_DEBUG);
		
		var width = me.chartCanvasSize.width;
		var height = me.chartCanvasSize.height;

		var chartTop = me.config.design.padding.top;

		var titleConf = me.config.chartCaptions;
		var titleColor = me.getAttributeForTheme(titleConf, 'color');
		var titleHeight = me.canvasHelper.getTextHeight(titleConf.font) / 2;

		var chart;

		var emptyTextColor = me.getAttributeForTheme(me.config.design, 'emptyTextColor');
		for (var chartId in me.drawInfo) {
			if (chartId == 'preview') {
				continue;
			}

			var drawn = true;
			var drawInfo = me.drawInfo[chartId];
			chart = drawInfo.chart;
			var chartHeight = drawInfo.computedHeight;

			var limits = { x1: me.chartLeft, y1: chartTop, x2: me.chartRight, y2: chartHeight + chartTop - 1 };
			me.drawingContext.id = chart.id;
			me.drawingContext.chartId = chart.id;
			me.drawingContext.group = chart.id;
			me.drawingContext.title = chart.title;
			me.drawingContext.indicatorLine = null;
			me.drawingContext.index = null;
			me.drawingContext.drawInfo = drawInfo;

			if (drawInfo.main === true) {
				me.drawingContext.lastCaptionTop = chartTop + titleHeight + titleConf.padding.top;
				drawInfo.indicators.forEach(function (indicator) {
					me.drawingContext.chartId = indicator.id;
					me.drawingContext.title = indicator.title;
					me.drawingContext.group = indicator.subchartID;
					me.drawingContext.id = indicator.id;
					drawIndicators(drawInfo, drawInfo.chart.xVals, limits, indicator, true);
				});
				
				me.drawingContext.title = chart.title;				
				drawn = drawDs(drawInfo, chart.xVals, limits, chart);
			} else {
				var parentInfo = me.drawInfo[chart.subchartID];
				me.drawingContext.group = chart.subchartID;
				drawn = drawIndicators(drawInfo, parentInfo.chart.xVals, limits, drawInfo.chart, false);
			}

			if (!drawn) {
				var borders = { x: limits.x1, y: limits.y1, width: me.chartCanvasSize.width, height: drawInfo.computedHeight };
				me.canvasHelper.centerText("N/A", emptyTextColor, me.config.design.emptyTextFont, borders);
			}

			if (chart.title) {
				me.canvasHelper.drawText(chart.title, titleColor, titleConf.font, me.realChartLeft + titleConf.padding.left, chartTop + titleHeight + titleConf.padding.top);
			}

			chartTop += chartHeight + me.getSizeBetweenCharts(drawInfo.last);
		}

		me.chartBottom = chartTop - me.getSizeBetweenCharts(true);
		me.previewTop = chartTop;
		return chartTop;
	};

	this.drawDataSet = function (drawInfo, xVals, ds, limits, mainMinMaxY) {
		var color = this.getValue(me.drawingContext.chartId, ds, me.drawingContext.index, 'color');
		var type = this.getValue(me.drawingContext.chartId, ds, me.drawingContext.index, 'type');

		var dataHeight = Math.abs(mainMinMaxY.max - mainMinMaxY.min);
		var height = limits.y2 - limits.y1;

		var factorY = height / dataHeight;
		drawInfo.factorY = factorY;
		drawInfo.top = limits.y1;

		if (type == 'line') {
			me.drawLines(ds, xVals, mainMinMaxY, limits, factorY);
		} else if (type == 'bar') {
			me.drawBars(ds, xVals, mainMinMaxY, limits, factorY);
		} else if (type == 'point') {
			me.drawPoints(ds, xVals, mainMinMaxY, limits, factorY);
		} else {
			me.drawCandle(ds, xVals, mainMinMaxY, limits, factorY);
		}

		return factorY;
	};

	this.getPreviewWidth = function () {
		return me.chartCanvasSize.width + me.rightAxisWidth;
	}

	this.drawPreview = function (yStart) {
		var yDatas = me.data.preview.yVals;

		var count = yDatas.length;
		var factorX = me.getPreviewWidth() / count;
		var x = 0;

		var drawInfo = me.drawInfo['preview'];
		var factorY = drawInfo.computedHeight / (drawInfo.minMaxY.max - drawInfo.minMaxY.min);

		var background = me.getAttributeForTheme(me.config.chart.preview, 'background');
		me.canvasHelper.drawFilledRectangle(me.chartLeft, yStart, me.chartCanvasSize.width + me.rightAxisWidth, drawInfo.computedHeight, background);

		var previewBottom = drawInfo.computedHeight + yStart;
		var color = me.getAttributeForTheme(me.config.chart.preview, 'color');
		me.canvasHelper.beginPath(me.chartLeft, previewBottom, { fillColor: color, width: me.config.chart.preview.thickness });
		yDatas.forEach(function (y) {
			var _x = me.chartLeft + factorX * x;
			var _y = previewBottom - factorY * (y - drawInfo.minMaxY.min);

			me.canvasHelper.lineTo(_x, _y);

			x++;
		});
		me.canvasHelper.lineTo(me.chartLeft + me.getPreviewWidth(), previewBottom);
		me.canvasHelper.closePath();
		me.canvasHelper.fill();

		var captions = me.data.preview.captions;

		var textHeight = me.canvasHelper.getTextHeight(me.config.chart.preview.hintFont);
		var separatorColor = me.getAttributeForTheme(me.config.chart.preview, 'separatorColor');
		var hintColor = me.getAttributeForTheme(me.config.chart.preview, 'hintColor');
		for (var index = 0; index < captions.length; index++) {
			var captionInfo = captions[index];
			var _x = me.chartLeft + factorX * captionInfo.index;

			if (captionInfo.index != 0) {
				me.canvasHelper.drawLine(_x, yStart + 1, _x, yStart + drawInfo.computedHeight - 2, separatorColor);
			}

			var _nextX;
			if (index < captions.length - 1) {
				_nextX = me.chartLeft + factorX * captions[index + 1].index;
			} else {
				_nextX = me.chartCanvasSize.width + me.rightAxisWidth + me.chartLeft;
			}

			var borders = { x: _x, y: yStart + drawInfo.computedHeight + 5 - textHeight, width: _nextX - _x, height: textHeight };
			me.canvasHelper.centerText(captionInfo.caption, hintColor, me.config.chart.preview.hintFont, borders);
		}
		
		if (me.config.markers.showInPreview===true && me.data.preview.trades){
			var trades = me.data.preview.trades;
			var _y = previewBottom - factorY * (drawInfo.minMaxY.max - drawInfo.minMaxY.min);
			var previewWidth = me.getPreviewWidth();
			trades.forEach(function (info) {
				var _x = me.chartLeft + factorX * info.from;
				var _x2 = me.chartLeft + factorX * info.to;
				var _width = _x2-_x;
				me.canvasHelper.drawFilledRectangle(_x, _y, _width, me.config.markers.previewHeight, me.config.markers.previewColor);
			});
		}
	}

	this.drawCandle = function (ds, xVals, minMaxY, limits, factorY, group) {
		var yVals = ds.yVals;
		var length = SQ4.Utils.minLength(xVals, yVals);

		var barWidth = me.candleWidth;
		var barHalf = Math.round(barWidth / 2 - 0.5);
		barWidth = barHalf * 2 + 1;

		var rightSide = me.chartLeft + me.chartCanvasSize.width;

		var upTradeColor = me.getValue(me.drawingContext.chartId, ds, 'upTradeColor', null, me.config.chart.candle.upTradeColor);
		var downTradeColor = me.getValue(me.drawingContext.chartId, ds, 'downTradeColor', null, me.config.chart.candle.downTradeColor);

		for (var i = 0; i < length; i++) {
			var xInfo = me.getXInfo(limits.x1, i, xVals);

			var y = yVals[i];
			var _x = xInfo.x;
			if (me.isOutOfScreen(_x)) {
				continue;
			}
			var _open = limits.y2 - factorY * (y.open - minMaxY.min);
			var _close = limits.y2 - factorY * (y.close - minMaxY.min);
			var _low = limits.y2 - factorY * (y.low - minMaxY.min);
			var _high = limits.y2 - factorY * (y.high - minMaxY.min);

			var barWidthForUse = barWidth;
			var startX = _x - barHalf;

			var lineStartX;
			if (startX < me.chartLeft) {
				barWidthForUse -= me.chartLeft - startX;
				startX = me.chartLeft;
				lineStartX = startX;
			} else {
				lineStartX = startX + barHalf;
			}

			if (startX + barWidthForUse > rightSide) {
				barWidthForUse = rightSide - startX;
			}

			var barStartY = _open < _close ? _open : _close;
			var barHeight = Math.abs(_open - _close);

			me.canvasHelper.drawLine(lineStartX, _high, lineStartX, _low, me.config.chart.candle.shadowColor);
			var color = y.open < y.close ? upTradeColor : downTradeColor;
			if (barHeight == 0) {
				me.canvasHelper.drawLine(startX, _open, startX + barWidthForUse, _open, me.config.chart.candle.shadowColor);
			} else {
				me.canvasHelper.drawFilledRectangle(startX, barStartY, barWidthForUse, barHeight, color);
			}

			me.drawingContext.candleMap[Math.round(startX)] = { min: _high, max: _low };

			me.storeTooltip(xInfo, y, 'chart', ds);
		};
	}

	this.drawPoints = function (ds, xVals, minMaxY, limits, factorY) {
		var yVals = ds.yVals;
		var length = SQ4.Utils.minLength(xVals, yVals);

		var color = me.getValue(me.drawingContext.chartId, ds, me.drawingContext.index, 'color');

		for (var i = 0; i < length; i++) {
			var y = yVals[i];

			var xInfo = me.getXInfo(limits.x1, i, xVals);
			var _x = Info.x;
			if (me.isOutOfScreen(_x)) {
				continue;
			}
			var _y = limits.y2 - factorY * (y - minMaxY.min);

			me.canvasHelper.drawFilledCircle(_x, _y, color, me.config.chart.point.radius)
			me.storeTooltip(xInfo, y, 'indicator', ds);
		};
	}

	this.getBarWidth = function (xVals, yVals) {
		var length = SQ4.Utils.minLength(xVals, yVals);

		var requiredSpaces = me.config.chart.bar.space * length;
		var barWidth = (me.chartCanvasSize.width - requiredSpaces) / length;

		if (barWidth > me.config.chart.bar.maxWidth) {
			barWidth = me.config.chart.bar.maxWidth;
		}
		return barWidth;
	}

	this.drawBars = function (ds, xVals, minMaxY, limits, factorY) {
		var yVals = ds.yVals;
		var length = SQ4.Utils.minLength(xVals, yVals);

		var barWidth = me.getBarWidth(xVals, yVals);

		var barHalf = barWidth / 2;

		var referentY;
		if (minMaxY.min < 0) {
			if (minMaxY.max > 0) {
				referentY = 0;
			} else {
				referentY = minMaxY.max;
			}
		} else {
			if (minMaxY.max < 0) {
				referentY = 0;
			} else {
				referentY = minMaxY.min;
			}
		}

		var startY = limits.y2 - factorY * (referentY - minMaxY.min);

		var rightSide = me.chartLeft + me.chartCanvasSize.width;

		var color = me.getValue(me.drawingContext.chartId, ds, me.drawingContext.index, 'color');
		for (var i = 0; i < length; i++) {
			var y = yVals[i];

			if (y == 0) { continue; }

			var xInfo = me.getXInfo(limits.x1, i, xVals);
			var _x = xInfo.x;
			if (me.isOutOfScreen(_x)) {
				continue;
			}
			var _y = limits.y2 - factorY * (y - minMaxY.min);

			var sens = me.config.tooltip.sensitivity;
			if (barWidth < 1) {
				me.canvasHelper.drawLine(_x, startY, _x, _y, color);
			} else {
				var barWidthForUse = barWidth;
				var startX = _x - barHalf;
				if (startX < me.chartLeft) {
					barWidthForUse -= (me.chartLeft - startX);
					startX = me.chartLeft;
				} else if (startX + barWidthForUse > rightSide) {
					barWidthForUse = rightSide - startX;
				}

				var barStartY = startY < _y ? startY : _y;
				var barHeight = Math.abs(startY - _y);
				if (barStartY + barHeight >= limits.y2) {
					barHeight = limits.y2 - barStartY;
				}

				me.canvasHelper.drawFilledRectangle(startX, barStartY, barWidthForUse, barHeight, color);
			}

			me.storeTooltip(xInfo, y, 'indicator', ds);
		};
	}

	this.drawLines = function (ds, xVals, minMaxY, limits, factorY) {
		var thickness = me.getValue(me.drawingContext.chartId, ds, me.drawingContext.index, 'thickness', me.config.chart.line.thickness);
		var pattern = me.getValue(me.drawingContext.chartId, ds, me.drawingContext.index, 'pattern', me.config.chart.line.pattern);
		var color = me.getValue(me.drawingContext.chartId, ds, me.drawingContext.index, 'color');

		var rightSide = me.chartLeft + me.chartCanvasSize.width;

		var length = SQ4.Utils.minLength(xVals, ds.yVals);
		var first = true;
		for (var i = 0; i < length; i++) {
			var y = ds.yVals[i];
			if (y == null) {
				continue;
			}

			var xInfo = me.getXInfo(limits.x1, i, xVals);
			var _x = xInfo.x;
			if (me.isOutOfScreen(_x)) {
				continue;
			}
			var _y = limits.y2 - factorY * (y - minMaxY.min);

			if (_y > limits.y2) {
				_y = limits.y2;
			} else if (_y < limits.y1) {
				_y = limits.y1;
			}

			if (first) {
				me.canvasHelper.beginPath(_x, _y, { strokeColor: color, width: thickness, lineDash: pattern });
				first = false;
			} else {
				me.canvasHelper.lineTo(_x, _y);
			}

			me.storeTooltip(xInfo, y, 'indicator', ds);
		};

		me.canvasHelper.stroke();
	}

	this.isIndicatorAllowed = function (indicatorId) {
		if (me.allowedIndicators == null) {
			return true;
		}

		return me.allowedIndicators.indexOf(indicatorId) > -1;
	}

	this.isTradeMarkersAllowed = function (id) {
		if (me.allowedTradeMarkers == null) {
			return true;
		}

		return me.allowedTradeMarkers.indexOf(id) > -1;
	}

	this.setAllowedIndicators = function (allowedIndicators) {
		this.allowedIndicators = allowedIndicators;
	}

	this.setAllowedTradeMarkers = function (allowedTradeMarkers) {
		this.allowedTradeMarkers = allowedTradeMarkers;
	}

	this.countHeight = function (canvasHeight) {
		function normalizeHeights(total) {
			var mainCount = me.data.charts.length;
			var subCount = separatedIndicators.length;

			var previewInfo = me.drawInfo['preview'];
			var freePixels = 0;
			if (previewInfo.computedHeight > me.config.chart.preview.maxHeight) {
				var delta = previewInfo.computedHeight - me.config.chart.preview.maxHeight;
				previewInfo.computedHeight = me.config.chart.preview.maxHeight;

				freePixels = delta;
			}

			separatedIndicators.forEach(function (indicator) {
				var drawInfo = me.drawInfo[indicator.id];
				if (drawInfo.computedHeight > me.config.chart.subchartMaxHeight) {
					var delta = drawInfo.computedHeight - me.config.chart.subchartMaxHeight;
					drawInfo.computedHeight = me.config.chart.subchartMaxHeight;

					freePixels += delta;
				}
			});

			if (freePixels > 0) {
				var part = freePixels / me.data.charts.length;
				me.data.charts.forEach(function (chart) {
					var drawInfo = me.drawInfo[chart.id];
					drawInfo.computedHeight += part;
				});
			}

			var minMainHeight = (me.config.chart.mainchartMinHeight / 100) * total;
			var minMainHeightOneChart = minMainHeight / me.data.charts.length;
			var morePixels = 0;
			me.data.charts.forEach(function (chart) {
				var drawInfo = me.drawInfo[chart.id];
				if (drawInfo.computedHeight < minMainHeightOneChart) {
					var delta = minMainHeightOneChart - drawInfo.computedHeight;
					drawInfo.computedHeight = minMainHeightOneChart;
					morePixels += delta;
				}
			});

			if (morePixels == 0) {
				return;
			}

			var previewHeight = previewInfo.computedHeight;
			var previewHeightHalf = previewHeight / 2;
			var newPreviewHeight = previewHeight - morePixels;
			if (newPreviewHeight > previewHeightHalf) {
				previewInfo.computedHeight = newPreviewHeight;
				return;
			}

			previewInfo.computedHeight = previewHeightHalf;
			morePixels -= previewHeightHalf;

			var part = morePixels / separatedIndicators.length;
			separatedIndicators.forEach(function (indicator) {
				var drawInfo = me.drawInfo[indicator.id];
				drawInfo.computedHeight -= part;
			});
		}

		SQ4.Utils.log('Preparing heights', SQ4.Consts.LOG_DEBUG);

		var separatedIndicators = [];
		var includedIndicators = [];
		me.data.indicators.forEach(function (indicator) {
			if (me.isIndicatorAllowed(indicator.id)) {
				if (indicator.displayOn == 'subchart') {
					separatedIndicators.push(indicator);
				} else {
					includedIndicators.push(indicator);
				}
			}
		});
		var sizeBetweenCharts = 0;

		var index = 0;
		var totalWeight = 0;
		var chartCount = me.data.charts.length;
		me.data.charts.forEach(function (chart) {
			var weight = chart.weight ? chart.weight : 1;
			var drawInfo = { weight: weight, chart: chart, main: true, indicators: [], trades:[] };
			me.drawInfo[chart.id] = drawInfo;

			totalWeight += weight;
			var lastChart = index == chartCount - 1 && separatedIndicators.length == 0;
			drawInfo.last = lastChart;
			var sizeBetween = me.getSizeBetweenCharts(lastChart);
			sizeBetweenCharts += sizeBetween;
			index++;
		});

		index = 0;
		chartCount = separatedIndicators.length;
		separatedIndicators.forEach(function (indicator) {
			var weight = indicator.weight ? indicator.weight : 1;
			var drawInfo = { weight: weight, chart: indicator, main: false };
			me.drawInfo[indicator.id] = drawInfo;

			totalWeight += weight;
			var lastChart = index == chartCount - 1;
			drawInfo.last = lastChart;
			var sizeBetween = me.getSizeBetweenCharts(lastChart);
			sizeBetweenCharts += sizeBetween;
			index++;
		});

		includedIndicators.forEach(function (indicator) {
			var parentDrawInfo = me.drawInfo[indicator.subchartID];
			if (parentDrawInfo && parentDrawInfo.indicators) {
				parentDrawInfo.indicators.push(indicator);
			}
		});

		var previewWeight = me.data.preview.weight ? me.data.preview.weight : 1;
		me.drawInfo['preview'] = { weight: previewWeight };
		totalWeight += previewWeight;

		var total = canvasHeight - sizeBetweenCharts;
		var partSize = (total) / totalWeight;

		me.data.charts.forEach(function (chart) {
			var drawInfo = me.drawInfo[chart.id];
			drawInfo.computedHeight = drawInfo.weight * partSize;
		});
		separatedIndicators.forEach(function (indicator) {
			var drawInfo = me.drawInfo[indicator.id];
			drawInfo.computedHeight = drawInfo.weight * partSize;
		});

		var previewInfo = me.drawInfo['preview'];
		previewInfo.computedHeight = previewInfo.weight * partSize;

		normalizeHeights(total);
	}

	this.countMinMax = function () {
		this.minMaxX = { min: null, max: null };
		//count min max value of each candle's chart + add some empty space
		me.data.charts.forEach(function (chart) {
			var iMinMaxX = SQ4.Utils.getMinMax(chart.xVals);
			me.minMaxX = SQ4.Utils.mergeMinMax(me.minMaxX, iMinMaxX);

			var drawInfo = me.drawInfo[chart.id];
			//evaluate minmax for each chart
			drawInfo.minMaxY = SQ4.Utils.getMinMax(chart.yVals);

			var diff = Math.abs(drawInfo.minMaxY.max - drawInfo.minMaxY.min);
			if (diff == 0) {
				diff = drawInfo.minMaxY.max;
			}
			drawInfo.minMaxY.min -= diff * me.config.chart.candle.candleGrowFactor;
			drawInfo.minMaxY.max += diff * me.config.chart.candle.candleGrowFactor;
			drawInfo.precision = chart.precision;
		});

		var tmp = {};
		me.data.indicators.forEach(function (indicator) {
			if (!me.isIndicatorAllowed(indicator.id)) {
				return;
			}

			//evaluate minmax for each indicator
			var minMaxY = { min: null, max: null };
			indicator.datasets.forEach(function (ds) {
				var iMinMaxY = SQ4.Utils.getMinMax(ds.yVals);
				minMaxY = SQ4.Utils.mergeMinMax(minMaxY, iMinMaxY);
			});

			var parentInfo = me.drawInfo[indicator.subchartID];
			if (indicator.displayOn == 'subchart') {
				//indicator is on separated chart				
				me.drawInfo[indicator.id].minMaxY = minMaxY;
				me.drawInfo[indicator.id].minMaxX = parentInfo.minMaxX;
				me.drawInfo[indicator.id].precision = me.config.indicator.precision;
			} else {
				//indicator is on chart with candles
				var parentMinMaxY = parentInfo.minMaxY;
				var maxGrow = minMaxY.max > parentMinMaxY.max;
				var minGrow = minMaxY.min < parentMinMaxY.min;

				//indicator oversize the parents candles
				if (maxGrow || minGrow) {
					var info = tmp[indicator.subchartID];
					if (!info) {
						info = {};
						tmp[indicator.subchartID] = info;
					}

					//put this bigger value to tmp array
					if (maxGrow && (typeof info.max === 'undefined' || info.max < minMaxY.max)) {
						info.max = minMaxY.max;
					}
					if (minGrow && (typeof info.min === 'undefined' || info.min > minMaxY.min)) {
						info.min = minMaxY.min;
					}
				}
			}
		});

		for (var id in me.drawInfo) {
			if (id === 'preview') {
				continue;
			}

			var growinfo = tmp[id];

			//growInfo exists - so indicator in candle chart has bigger value than candles - so the area for candle and indicator will be extended
			if (typeof growinfo != 'undefined') {
				var info = me.drawInfo[id];
				var minMaxY = info.minMaxY;

				var delta = Math.abs(minMaxY.max - minMaxY.min);
				var growValue = delta * me.config.chart.candle.indicatorGrowFactor;
				var newMinMax = { min: minMaxY.min, max: minMaxY.max };

				var growBoth = typeof growinfo.min != 'undefined' && typeof growinfo.max != 'undefined'

				if (growBoth) {
					newMinMax.min -= growValue;
					newMinMax.max += growValue;
				} else {
					if (growinfo.min) {
						newMinMax.min -= 2 * growValue;
					} else {
						newMinMax.max += 2 * growValue;
					}
				}

				if (typeof growinfo.min != 'undefined' && newMinMax.min < growinfo.min) {
					newMinMax.min = growinfo.min;
				}

				if (typeof growinfo.max != 'undefined' && newMinMax.max > growinfo.max) {
					newMinMax.max = growinfo.max;
				}

				info.minMaxY = newMinMax;
			}
		}

		me.drawInfo['preview'].minMaxY = SQ4.Utils.getMinMax(me.data.preview.yVals);
	}

	this.getSizeBetweenCharts = function (lastChart) {
		var axisHeight = lastChart ? me.canvasHelper.getTextHeight(me.config.axis.font) * .8 : 0;
		var chartSpaces = !lastChart ? 0 : me.config.chart.preview.space;
		return chartSpaces + me.config.axis.axisOverLine + axisHeight;
	}

	this.countChartCanvasSize = function () {
		var width = me.canvasHelper.getWidth();
		var height = me.canvasHelper.getHeight();

		var design = me.config.design;
		width = width - design.padding.left - design.padding.right;
		height = height - design.padding.top - design.padding.bottom;

		this.chartCanvasSize = { width: Math.round(width), height: Math.round(height) };
	}

	this.evalGlobalXValsArray = function () {
		me.xVals = [];

		var maxCount = 0;
		for (var id in me.drawInfo) {
			var info = me.drawInfo[id];

			if (info.main === true) {
				var minOffset = null;
				var lastX = null;
				var xVals = info.chart.xVals;
				var length = xVals.length;
				if (maxCount < length) {
					maxCount = length;
				}
				for (var i = 0; i < xVals.length; i++) {
					var x = xVals[i];

					if (me.xVals.indexOf(x) == -1) {
						me.xVals.push(parseInt(x));
					}

					if (lastX != null) {
						var delta = x - lastX;
						if (minOffset == null || minOffset < delta) {
							minOffset = delta;
						}
					}
					lastX = x;
				}

				info.oneWidth = minOffset;
			}
		}

		var requiredSpaces = me.config.chart.bar.space * maxCount;
		me.candleWidth = (me.chartCanvasSize.width - requiredSpaces) / maxCount;
		if (me.candleWidth > me.config.chart.candle.maxWidth) {
			me.candleWidth = me.config.chart.candle.maxWidth;
		}

		me.candleWidthHalf = me.candleWidth / 2;
		me.xVals.sort(function(a, b) {
		  return a - b;
		});
	}

	this.countXVals = function () {
		this.evalGlobalXValsArray();
		me.factorX = me.chartCanvasSize.width / (me.xVals.length - 1);
	}

	this.getX = function (timestamp) {
		var index = me.xVals.indexOf(timestamp);
		if (index > -1) {
			return me.factorX * index;
		}
	}

	this.getXInfo = function (startX, localIndex, xVals) {
		var min, max;

		var timestamp = xVals[localIndex];

		var xVal = startX + me.getX(timestamp);

		var prevIndex = localIndex - 1;
		var nextIndex = localIndex + 1;

		if (me.isForex()) {
			min = xVal - me.candleWidthHalf;

			if (nextIndex == xVals.length) {
				max = me.chartRight;
			} else {
				var nextTimestamp = xVals[nextIndex];
				var nextX = startX + me.getX(nextTimestamp);
				max = nextX - me.candleWidthHalf;
			}
		} else {
			max = xVal + me.candleWidthHalf;

			if (prevIndex == -1) {
				min = me.chartLeft;
			} else {
				var prevTimestamp = xVals[prevIndex];
				var prevX = startX + me.getX(prevTimestamp);
				min = prevX + me.candleWidthHalf;
			}
		}

		return { x: xVal, min: min, max: max, caption: me.domainAxisRenderer.getCaption(timestamp, false, 'dd.mm.yyyy HH:MM') };
	}

	this.isForex = function () {
		var futures = this.data.type === 'futures';
		return !futures;
	}

	this.setData = function (data, skipClear, shift) {
		SQ4.Utils.log('Handling loaded data', SQ4.Consts.LOG_DEBUG);
		
		this.data = data;
		this.tooltipManager.clear();

		if (typeof (skipClear) == 'undefined' || skipClear == false) {
			this.clear();
		}
		this.minMaxX = null;
		if (!data) {
			return;
		}

		this.drawInfo = {};

		this.countChartCanvasSize();
		this.countHeight(me.chartCanvasSize.height);
		this.countMinMax();
		this.countAxises();

		var empty = this.minMaxX.min == null || this.minMaxX.max == null;
		if (empty) {
			this.minMaxX = { min: 0, max: 1 };
		}

		this.countXVals();

		me.chartLeft = me.config.design.padding.left;
		me.realChartLeft = me.chartLeft;
		me.chartRight = me.chartLeft + me.chartCanvasSize.width;
		if (shift) {
			me.chartLeft += shift;
		}

		me.chartLeftRelativeToData = me.chartLeft - me.minMaxX.min * me.factorX;
		this.drawAxises();

		var previewTop = this.drawData();
		me.chartLeft = me.config.design.padding.left;

		this.drawPreview(previewTop);
		this.drawBorders(previewTop);
		
		if (this.highlightedTradeDetail!=null && me.config.tradeSelectedHandler!=null){
			me.config.tradeSelectedHandler(this.highlightedTradeDetail);
		}
	};

	this.isOutOfScreen = function (x) {
		return x < me.config.design.padding.left || x > me.chartRight;
	}

	this.drawBorders = function (previewTop) {
		var padding = me.config.design.padding;
		var width = me.chartCanvasSize.width + me.rightAxisWidth;

		//rectangle around whole component
		var borderColor = me.getAttributeForTheme(me.config.design, 'borderColor');
		me.canvasHelper.drawRectangle(padding.left, padding.top, borderColor, width, me.chartCanvasSize.height, { width: me.config.design.borderWidth });

		//separator between right axis and content
		me.canvasHelper.drawLine(me.chartRight + 1, padding.top, me.chartRight + 1, previewTop, borderColor, { width: me.config.design.borderWidth });
		//separator between x-axis and preview block
		me.canvasHelper.drawLine(padding.left, previewTop, width + padding.left, previewTop, borderColor, { width: me.config.design.borderWidth });
	}

	this.drawTradeMarkers = function (chart, factorY, limits, minMaxY, barWidth) {
		function findNearestFromList(list, size, x) {
			var bestItem;
			var nearest = size + 1;

			for (var index = x - size; index <= x + size; index++) {
				var value = list[index];
				if (typeof value === 'undefined') {
					continue;
				}

				var length = Math.abs(x - index);
				if (length < nearest) {
					nearest = length;
					bestItem = value;
				}
			}

			return bestItem;
		}
		
		function drawArrow(up, left, top, height){	
			var width = me.config.markers.openArrowWidth;
			
			var points = [];
			if (up){
				var barTop = top+width/2+4;
				points.push({ x: left+(width-1)/2+1, y: top }, { x: left + width, y: barTop }, { x: left, y: barTop });
				me.canvasHelper.drawFilledRectangle(left+5, barTop, (width-1)/2, height, me.config.markers.openTextColor);				
			}else{
				var barBottom = top+height;
				points.push({ x: left+(width-1)/2, y: barBottom+width/2+4 }, { x: left, y: barBottom }, { x: left + width, y: barBottom });
				me.canvasHelper.drawFilledRectangle(left+4, top, (width-1)/2+1, height, me.config.markers.openTextColor);			
			}
			
			me.canvasHelper.drawFilledPolygon(points, me.config.markers.openTextColor, me.config.markers.openTextColor);
		}

		function drawCaption(text, x, y, borders, color, squareColor, arrow) {
			x = Math.round(x);

			var space = me.config.markers.squareCaptionSpace;

			var padding = me.config.markers.squarePadding;

			var lines = text.split("\n");
			var textHeight = lines.length * me.canvasHelper.getTextHeight(me.config.markers.font);
			var textWidth = me.canvasHelper.getTextWidth(text, me.config.markers.font);

			var candleLimits = findNearestFromList(me.drawingContext.candleMap, Math.round(textWidth), x);

			var newY;

			if (!candleLimits) {
				newY = y + space;
			} else {
				newY = candleLimits.max + space;
			}

			if (newY < borders.y1 + textHeight / 1.5) {
				if (!candleLimits) {
					newY = y - textHeight - space;
				} else {
					newY = candleLimits.min - textHeight - space;
				}
			}

			var newX = x - textWidth / 2;

			if (newX < borders.x1) {
				newX = borders.x1;
			}

			var totalWidth = textWidth + 2 * padding;
			if (newX + totalWidth > borders.x2) {
				newX = borders.x2 - totalWidth;
			}

			var arrowWidth = typeof(arrow)==='undefined'?0:me.config.markers.openArrowWidth+2*padding;
			
			var options = { stroke: false, fill: true, fillColor: squareColor, radius: 5 }
			me.canvasHelper.drawRoundedRectangle(newX - padding, newY, textWidth + 2 * padding+ arrowWidth, textHeight, options);

			if (typeof(arrow)!=='undefined'){
				newX+=arrowWidth;
			}
			
			var rowHeight = me.canvasHelper.getTextHeight(me.config.markers.font);
			var y = newY + rowHeight / 1.5 + 1;
			var startY = newY;
			for (var i in lines) {
				me.canvasHelper.drawText(lines[i], color, me.config.markers.font, newX, y);
				y += rowHeight;
			}
			
			if (typeof(arrow)!=='undefined'){
				drawArrow(arrow=='up', newX-arrowWidth, startY+padding, y-startY-30);
			}			
		}

		function drawTriangles(x1, y1, x2, y2, size, drawOpen, drawClose) {
			var points = [];

			if (drawOpen === true) {
				points.push({ x: x1, y: y1 }, { x: x1 - size, y: y1 - size }, { x: x1 - size, y: y1 + size });
				me.canvasHelper.drawFilledPolygon(points, me.config.markers.triangleColor, me.config.markers.triangleFillColor);
			}

			if (drawClose === true) {
				points = [];
				points.push({ x: x2, y: y2 }, { x: x2 + size, y: y2 - size }, { x: x2 + size, y: y2 + size });
				me.canvasHelper.drawFilledPolygon(points, me.config.markers.triangleColor, me.config.markers.triangleFillColor);
			}
		}

		function countPips(open, close, tickSize, buy) {
			if (buy === true) {
				return ((close - open) / tickSize);
			} else {
				return ((open - close) / tickSize);
			}
		}

		function normalizeY(y, min, max) {
			if (y < min) {
				return min;
			} else if (y > max) {
				return max;
			}

			return y;
		}

		var tradeMarkers = chart.tradeMarkers;
		if (!tradeMarkers || !me.isTradeMarkersAllowed(chart.id)) {
			return;
		}

		var design = me.config.markers;

		var maxY = limits.y2;
		var minY = limits.y2 - factorY * (minMaxY.max - minMaxY.min);
		tradeMarkers.forEach(function (tm) {
			var openX = me.getX(tm.openTimestamp);
			var openY = tm.openValue;

			var closeX = me.getX(tm.closeTimestamp);
			var closeY = tm.closeValue;

			if (!openX) {
				openX = -1;
			}
			if (!closeX) {
				closeX = me.chartRight + 1;
			}

			var long = tm.long;

			var _openX = limits.x1 + openX;
			var _openY = limits.y2 - factorY * (openY - minMaxY.min);

			var _closeX = limits.x1 + closeX;
			var _closeY = limits.y2 - factorY * (closeY - minMaxY.min);

			var profit = (long && openY < closeY) || (!long && openY > closeY);

			if (_closeX < me.chartLeft || _openX > me.chartRight) {
				return;
			}

			if (_openX < me.chartLeft) {
				_openX = me.chartLeft;
			}

			if (_closeX > me.chartRight) {
				_closeX = me.chartRight;
			}

			_openY = normalizeY(_openY, minY, maxY);
			_closeY = normalizeY(_closeY, minY, maxY);

			var lineColor;
			if (long) {
				if (profit) {
					lineColor = design.lineLongProfitColor;
				} else {
					lineColor = design.lineLongLossColor;
				}
			} else {
				if (profit) {
					lineColor = design.lineShortProfitColor;
					lineColor = design.lineShortProfitColor;
				} else {
					lineColor = design.lineShortLossColor;
				}
			}

			var width = 1;
			if (me.highlightedTradeId==tm.id) {
				width = me.config.markers.highlightedLineWidth;
			}
			me.canvasHelper.drawLine(_openX, _openY, _closeX, _closeY, lineColor, { width:width, lineDash: me.config.markers.linePattern });
			drawTriangles(_openX, _openY, _closeX, _closeY, barWidth, true, true);

			
			//open bubble
			var openText = '';
			
			if (design.showCloseTicket===true){
				openText+=design.openCloseTextTicket.replace('{ticket}', tm.id);
			}
			if (design.showOpenCloseValue===true){
				var openCmdText = long ? design.buyText : design.sellText;
				var strValue = me.YAxisRenderer.getCaption(tm.openValue, me.config.axis.shortCaptions, me.drawingContext.drawInfo.precision);
				
				if (openText!=''){
					openText+='\n';
				}
				openText += openCmdText.replace('{value}', strValue);
			}
			
			if (openText!=''){				
				drawCaption(openText, _openX+ design.openArrowWidth, _openY, limits, design.openTextColor, design.openSquareColor, long?'up':'down');
			}

			var delta = closeY - openY;
			var format = me.drawingContext.drawInfo.precision;
			var plCoreVal = me.data.pipValue ? parseFloat(countPips(openY, closeY, me.data.pipValue, long).toFixed(format)) : 0;
			var pl = me.data.pipValue ? parseFloat(countPips(openY, closeY, me.data.pipValue, long).toFixed(format)) + ' pips' : '';		
			tm.pl = pl;
			
			//close bubble
			var strValue = me.YAxisRenderer.getCaption(tm.closeValue, me.config.axis.shortCaptions, me.drawingContext.drawInfo.precision);

			var closeText = '';
			
			if (design.showCloseTicket===true){
				closeText+=design.openCloseTextTicket.replace('{ticket}', tm.id);
			}
			if (design.showOpenCloseValue===true){
				if (closeText!=''){
					closeText+='\n';
				}
				closeText+=design.closeTextValue.replace('{value}', strValue);				
			}
			if (design.showClosePL===true){
				if (closeText!=''){
					closeText+='\n';
				}
				closeText+=design.closeTextPL.replace('{plValue}', pl);				
			}

			if (closeText!=''){
				var closeTextColor,closeSquareColor;
				if (plCoreVal==0){
					closeTextColor = design.closeTextZeroColor;
					closeSquareColor = design.closeSquareZeroColor;
				}else if (plCoreVal<0){
					closeTextColor = design.closeTextLossColor;
					closeSquareColor = design.closeSquareLossColor;
				}else{
					closeTextColor = design.closeTextProfitColor;
					closeSquareColor = design.closeSquareProfitColor;
				}
				drawCaption(closeText, _closeX, _closeY, limits, closeTextColor, closeSquareColor);
			}

			
			me.drawingContext.drawInfo.trades.push({from:_openX, to:_closeX, id:tm.id, index:tm.index});
			//me.storeTooltipForTraderMarker(tm, pl, _openX, _closeX, chart);
		});
	}

	this.refresh = function (clear) {
		if (me.data == null) {
			return;
		}
		this.setData(me.data, !clear);
	}

	this.getPreviewSize = function () {
		var previewInfo = me.drawInfo['preview'];
		if (previewInfo) {
			return previewInfo.computedHeight;
		}

		return 0;
	}

	this.storeTooltipForTraderMarker = function (tm, pl, x1, x2, chart) {
		if (!me.config.tooltipHandler || !me.drawingContext.title) {
			return;
		}

		var yCaption = {
			id: tm.id,
			openValue: this.YAxisRenderer.getCaption(tm.openValue, false, me.drawingContext.drawInfo.precision),
			closeValue: this.YAxisRenderer.getCaption(tm.closeValue, false, me.drawingContext.drawInfo.precision),
			openTime: this.domainAxisRenderer.getCaption(tm.realOpenTimestamp, false, 'dd.mm.yyyy HH:MM:ss'),
			closeTime: this.domainAxisRenderer.getCaption(tm.realCloseTimestamp, false, 'dd.mm.yyyy HH:MM:ss'),
			isLong: tm.long,
			pl: pl
		};

		me.tooltipManager.addRegion(x1 - 1, x2 + 1, null, yCaption, 'trade', me.drawingContext);
	}

	this.storeTooltip = function (xInfo, y, kind, ds) {
		if (!me.config.tooltipHandler || !me.drawingContext.title) {
			return;
		}

		var yCaption;
		if (typeof y === 'object') {
			yCaption = {
				open: this.YAxisRenderer.getCaption(y.open, false, me.drawingContext.drawInfo.precision),
				close: this.YAxisRenderer.getCaption(y.close, false, me.drawingContext.drawInfo.precision),
				high: this.YAxisRenderer.getCaption(y.high, false, me.drawingContext.drawInfo.precision),
				low: this.YAxisRenderer.getCaption(y.low, false, me.drawingContext.drawInfo.precision)
			};
		} else {
			yCaption = this.YAxisRenderer.getCaption(y, false, me.drawingContext.drawInfo.precision);
		}

		me.tooltipManager.addRegion(xInfo.min, xInfo.max, xInfo.caption, yCaption, kind, me.drawingContext);
	}

	this.getGroup = function (y, mainGroupOnly) {
		var top = me.config.design.padding.top;

		for (var chartId in me.drawInfo) {
			if (chartId == 'preview') {
				continue;
			}

			var drawInfo = me.drawInfo[chartId];
			if (y <= drawInfo.computedHeight + top) {
				if (!mainGroupOnly) {
					return chartId;
				}
				if (drawInfo.main === true) {
					return chartId;
				} else {
					return me.drawInfo[drawInfo.chart.subchartID].chart.id;
				}
			}
			top += drawInfo.computedHeight + me.getSizeBetweenCharts(drawInfo.last);
		}
	}

	this.evalTooltips = function (x, y) {
		if (x > me.chartRight || x < me.chartLeft) {
			return;
		}
		var group = me.getGroup(y, true);
		if (!group) {
			return;
		}

		var result = this.tooltipManager.get(x);

		var date = new Date(x);
		result.date = date.format('dd.mm.yyyy');
		result.time = date.format('HH:MM');
		return result;
	}

	this.clearTooltips = function () {
		this.tooltipManager.restore();
	}

	this.countChartCanvasSize();

	this.clearAreas = function (x, width) {
		var chartTop = me.config.design.padding.top;
		var chart;
		var backgroundColor = me.getAttributeForTheme(me.config.design, 'borderColor');
		for (var chartId in me.drawInfo) {
			if (chartId == 'preview') {
				continue;
			}

			var drawInfo = me.drawInfo[chartId];
			var chartHeight = drawInfo.computedHeight;

			me.canvasHelper.drawFilledRectangle(x, chartTop, width, chartHeight, backgroundColor);
			chartTop += chartHeight + me.getSizeBetweenCharts(drawInfo.last);
		}
	}

	this.shiftContent = function (delta) {
		me.setData(me.data, false, delta);
	}

	this.highlightTrade = function (tradeId) {
		this.highlightedTradeId = tradeId;
	}

	this.clearHighlightTrades = function () {
		this.highlightedTradeId = null;
		this.highlightedTradeIndex = null;
		this.highlightedTradeDetail = null;
	}
};

SQ4.StockChartBuffer = function (config, chartHelper, stockChart) {
	var me = this;
	this.buffer = [];
	this.config = config;
	this.chartHelper = chartHelper;
	this.stockChart = stockChart;
	this.preview = null;

	this.normalizeData = function (data) {
		function normalizeChart(chart) {
			if (chart.tradeMarkers) {
				chart.tradeMarkers.forEach(function (marker) {
					var open = marker.open;
					var close = marker.close;

					marker.openTimestamp = new Date(open).getTime();
					marker.closeTimestamp = new Date(close).getTime();

					var realOpen = typeof marker.realOpen === 'undefined' || marker.realOpen == null ? open : marker.realOpen;
					var realClose = typeof marker.realClose === 'undefined' || marker.realClose == null ? open : marker.realClose

					marker.realOpenTimestamp = new Date(realOpen).getTime();
					marker.realCloseTimestamp = new Date(realClose).getTime();
				});
			}

			if (chart.xVals && chart.xVals.length > 0 && (typeof chart.xVals[0] == 'string')) {
				var newxVals = [];
				chart.xVals.forEach(function (xVal) {
					var date = new Date(xVal);
					var transformedDate = date.getTime();
					newxVals.push(transformedDate);
				});
				chart.xVals = newxVals;
			}
		}

		if (!data.preview) {
			data.preview = {};
		}

		if (!data.preview.captions) {
			data.preview.captions = [];
		}

		if (!data.preview.yVals) {
			data.preview.yVals = [];
		}

		if (!data.charts) {
			data.charts = [];
		}

		if (!data.indicators) {
			data.indicators = [];
		}

		data.charts.forEach(function (chart) {
			normalizeChart(chart);
		});
	}

	this.clear = function () {
		me.buffer = [];
		me.preview = null;
	}

	this.add = function (data) {
		// returns new array which is created from original buffered datas and the new data
		// data is returned sorted by its 'from' attribute		
		var result = [];

		var newDataAdded = false;
		me.buffer.forEach(function (iData) {
			if (iData.from < data.from || newDataAdded == true) {
				result.push(iData);
			} else {
				newDataAdded = true;
				result.push(data);
				result.push(iData);
			}
		});

		if (newDataAdded === false) {
			result.push(data);
		}

		return result
	}

	function callErrorHandler(message, exception) {
		if (me.config.errorHandler) {
			me.config.errorHandler(message, exception);
		}
	}

	this.merge = function (data1, data2) {
		function copyTrades(resultChart, sourceChart) {
			var helpMap = {};
			if (resultChart.tradeMarkers) {
				resultChart.tradeMarkers.forEach(function (tm) {
					helpMap[tm.id] = true;
				});
			}

			if (sourceChart.tradeMarkers) {
				if (!resultChart.tradeMarkers) {
					resultChart.tradeMarkers = [];
				}

				sourceChart.tradeMarkers.forEach(function (tm) {
					if (helpMap[tm.id]) {
						return;
					}

					resultChart.tradeMarkers.push(tm);
				});
			}
		}

		function copy(resultChart, sourceChart, resultIndicators, sourceIndicators, minMax) {
			var indexes = SQ4.Utils.getArrayIndexes(sourceChart.xVals, minMax.min, minMax.max);

			var fromIndex, toIndex;
			if (!indexes.index1 || !indexes.index2) {
				fromIndex = 0;
				toIndex = sourceChart.xVals.length - 1;
			} else {
				fromIndex = indexes.index1;
				toIndex = indexes.index2;
			}

			for (var i = fromIndex; i <= toIndex; i++) {
				resultChart.xVals.push(sourceChart.xVals[i]);
				resultChart.yVals.push(sourceChart.yVals[i]);
			}

			for (var indicatorIndex = 0; indicatorIndex < resultIndicators.length; indicatorIndex++) {
				var resultIndicator = resultIndicators[indicatorIndex];
				var sourceIndicator = sourceIndicators[indicatorIndex];

				if (resultIndicator.subchartID != resultChart.id) {
					continue;
				}

				for (var dsI = 0; dsI < resultIndicator.datasets.length; dsI++) {
					var resultDs = resultIndicator.datasets[dsI];
					var sourceDs = sourceIndicator.datasets[dsI];

					for (var i = fromIndex; i <= toIndex; i++) {
						resultDs.yVals.push(sourceDs.yVals[i]);
					}
				}
			}
		}

		//data2 is within data1
		if (data1.to >= data2.to && data1.from <= data2.from) {
			return data1;
		}

		var result = data1;
		var minMax = me.findMinMax(data2.data, data1.to + 1, data2.to);
		for (var i = 0; i < result.data.charts.length; i++) {
			var resultChart = result.data.charts[i];
			var sourceChart = data2.data.charts[i];

			copy(resultChart, sourceChart, result.data.indicators, data2.data.indicators, minMax);
			copyTrades(resultChart, sourceChart);
		}

		result.to = data2.to;
		result.data.area_to = data2.data.area_to;

		return result;
	}

	this.store = function (data, from, to) {
		me.normalizeData(data);

		if (me.preview == null && data.preview) {
			me.preview = data.preview;
		}

		if (me.config.data.cache === false) {
			return;
		}

		var bufferItem = { from: from, to: to, data: data };
		var tmpBuffer = me.add(bufferItem);

		var newBuffer = [];
		var lastAddedItem = null;
		tmpBuffer.forEach(function (tmpItem) {
			if (lastAddedItem == null || lastAddedItem.to + 1 < tmpItem.from) {
				newBuffer.push(tmpItem);
				lastAddedItem = tmpItem;
			} else {
				var newResult = me.merge(lastAddedItem, tmpItem);

				//replace last element by the merged one
				newBuffer.pop();
				newBuffer.push(newResult);

				lastAddedItem = newResult;
			}
		});

		me.buffer = newBuffer;
	}

	this.loadByTradeIndex = function(from, to, zoomWidth, index){
		me.performLoad(from, to, from, to, zoomWidth, true, index);
	}

	this.load = function (from, to, zoomWidth, dateTimeRange) {
		if (dateTimeRange === true || me.config.data.cache === false) {
			me.performLoad(from, to, from, to, zoomWidth, dateTimeRange, null);
			return;
		}

		var findResult = me.find(from, to);
		if (findResult.foundData != null) {
			me.handleData(findResult.foundData.data, from, to);
			return;
		}

		me.performLoad(findResult.from, findResult.to, from, to, zoomWidth, dateTimeRange, null);
	}

	this.findMinMax = function (data, from, to) {
		var indexes = {};
		for (var iChart = 0; iChart < data.charts.length; iChart++) {
			indexes[iChart] = 0;
		}

		var globalIndex = data.area_from;
		var min = null;
		var max = null;

		while (globalIndex <= to) {
			var lowest = null;

			for (var iChart = 0; iChart < data.charts.length; iChart++) {
				var index = indexes[iChart];
				var chart = data.charts[iChart];
				if (index < chart.xVals.length) {
					var value = chart.xVals[index];
					if (lowest == null || value < lowest) {
						lowest = value;
					}
				}
			}

			if (lowest == null) {
				break;
			}

			for (var iChart = 0; iChart < data.charts.length; iChart++) {
				var index = indexes[iChart];
				var chart = data.charts[iChart];
				if (index < chart.xVals.length) {
					var value = chart.xVals[index];
					index++;
					indexes[iChart] = index;
				}
			}

			if (globalIndex >= from && globalIndex <= to) {
				if (min == null) {
					min = lowest;
				}

				max = lowest;
			}

			globalIndex++;
		}

		return { min: min, max: max };
	}

	this.take = function (data, from, to) {
		if (data == null) {
			var findResult = me.find(from, to);
			data = findResult.foundData.data;
		}

		var result = JSON.parse(JSON.stringify(data))
		result.preview = me.preview;

		if (me.config.data.cache === false) {
			return result;
		}

		var minMax = me.findMinMax(data, from, to);
		var index1, index2;
		result.charts.forEach(function (chart) {
			var indexes = SQ4.Utils.getArrayIndexes(chart.xVals, minMax.min, minMax.max);
			
			chart.xVals = SQ4.Utils.subArray(chart.xVals, indexes.index1, indexes.index2);
			chart.yVals = SQ4.Utils.subArray(chart.yVals, indexes.index1, indexes.index2);

			var newTrades = []
			chart.tradeMarkers.forEach(function (tm){
				if (tm.closeTimestamp<=minMax.min || tm.openTimestamp>=minMax.max){
					return;
				}
				
				newTrades.push(tm);
			});
			chart.tradeMarkers = newTrades;
			
			result.indicators.forEach(function (indicator) {
				if (chart.id != indicator.subchartID) {
					return;
				}

				indicator.datasets.forEach(function (ds) {
					ds.yVals = SQ4.Utils.subArray(ds.yVals, indexes.index1, indexes.index2);
				});
			});
		});

		result.area_from = data.from;
		result.area_to = data.to;
		return result;
	}

	this.isInCache = function (from, to) {
		var findResult = me.find(from, to);
		return findResult.foundData != null;
	}

	this.handleData = function (data, from, to, dateTimeRange, selectedTradeId) {
		try {
			if (dateTimeRange === true) {
				me.stockChart.updateRegion(from, to);
			}

			if (selectedTradeId!=null){
				me.chartHelper.highlightedTradeId = selectedTradeId;
			}
			me.chartHelper.setData(me.take(data, from, to));
			me.stockChart.updateRegionCaption();
		} catch (e) {
			SQ4.Utils.log('Error while setting data:' + e, SQ4.Consts.LOG_ERROR);
			throw e;
		}
	}

	this.performLoad = function (loadFrom, loadTo, from, to, zoomWidth, dateTimeRange, tradeIndex) {
		if (typeof (zoomWidth) === 'undefined') {
			zoomWidth = null;
		}

		var ctx = {
			width: Math.floor(me.chartHelper.chartCanvasSize.width),
			minX: loadFrom,
			maxX: loadTo,
			zoomWidth: zoomWidth,
			trade: tradeIndex,
			dataLoadedHandler: function (data) {
				try {
					if (dateTimeRange === true) {
						loadFrom = data.area_from;
						loadTo = data.area_to;
						from = data.area_from;
						to = data.area_to;
					}
					me.store(data, loadFrom, loadTo);

					var selectedTrade = data.selectedTradeId; 
					if (me.config.data.cache !== false) {
						data = null;
					}
					me.handleData(data, from, to, dateTimeRange, selectedTrade);
				} catch (e) {
					SQ4.Utils.log('Error while setting data:' + e, SQ4.Consts.LOG_ERROR);
					callErrorHandler('Error while setting data', e);
				}
			}
		};

		try {
			me.config.dataLoader(ctx);
		} catch (e) {
			SQ4.Utils.log('Error while loading data:' + e, SQ4.Consts.LOG_ERROR);
			callErrorHandler('Error while loading data', e);
		}
	}

	this.find = function (from, to) {
		var found = null;
		var foundPart = false;

		me.buffer.forEach(function (item) {
			if (found != null || foundPart == true) {
				return;
			}

			var firstX = item.from;
			var lastX = item.to;

			if (from >= firstX) {
				if (to <= lastX) {
					found = item
				} else if (from <= lastX) {
					from = lastX + 1;
					foundPart = true;
				}
			} else if (to <= lastX && to >= firstX) {
				to = firstX - 1;
				foundPart = true;
			}
		});

		return {
			foundPart: foundPart,
			from: from,
			to: to,
			foundData: found
		}
	}
}

SQ4.StockChart = function (cfg) {
	var me = this;

	this.tradeSelectedHandler = null;
	this.widthInfo = null;
	this.heightInfo = null;
	this.currentDataRegion = {};
	this.origDataRegion = {};
	this.config = SQ4.Utils.extend(cfg, SQ4.StockConst.DEFAULT_CONFIG);

	this.originalX = null;
	this.datasPerPixel = null;

	this.scroller = null;
	this.scrollerContent = null;

	this.cursorCrossMode = false;

	this.minimumZoom;
	this.maximumZoom;

	this.moving = false;
	this.localShiftX = null;
	this.mouseDown = false;
	this.mouseMoved = false;
	
	this.totalTrades = 0;

	SQ4.Utils.setLogConfig(this.config.logLevel, 'StockChart');

	if (!this.config.canvasId) {
		SQ4.Utils.log('No canvasId is set. Cannot continue.', SQ4.Consts.LOG_ERROR);
		return;
	}

	this.canvasWrapper = document.getElementById(this.config.canvasId);
	if (!this.canvasWrapper) {
		SQ4.Utils.log('No element with id:' + this.config.canvasId + ' was found. Cannot continue.', SQ4.Consts.LOG_ERROR);
		return;
	}
	if (this.canvasWrapper.tabIndex == -1) {
		this.canvasWrapper.tabIndex = 1;
	}

	var canvases = this.canvasWrapper.getElementsByTagName('canvas');
	if (canvases.length != 1) {
		SQ4.Utils.log('No unique canvas was found. Cannot continue.', SQ4.Consts.LOG_ERROR);
		return;
	}
	this.canvas = canvases[0];

	this.canvasHelper = new SQ4.CanvasHelper(this.canvas);
	this.chartHelper = new SQ4.StockChartHelper(this.canvasHelper, this.config);
	this.crossDrawer = new SQ4.StockCrossDrawer(this.config, this.canvasWrapper);

	this.dataSource = new SQ4.StockChartBuffer(this.config, this.chartHelper, this);

	initialize();

	function initResizer() {
		me.scroller = document.createElement("div");
		me.scroller.className = "resizeWrapper";
		me.scroller.style = "position:absolute;white-space:nowrap;cursor: w-resize;";

		var ratio = me.canvasHelper.ratio;
		var resizerSize = 1 / ratio;
		me.leftResizer = document.createElement("div");
		me.leftResizer.className = "resizer";
		me.leftResizer.style = me.config.zoom.scrollerSideLeftCss + "; height:100%; display:inline-block;";

		me.rightResizer = document.createElement("div");
		me.rightResizer.className = "resizer";
		me.rightResizer.style = me.config.zoom.scrollerSideRightCss + "; height:100%; display:inline-block;";

		me.scrollerContent = document.createElement("div");
		me.scrollerContent.className = "content";
		me.scrollerContent.style = me.config.zoom.scrollerContentCss + "; height:100%; display:inline-block;";

		me.scroller.appendChild(me.leftResizer);
		me.scroller.appendChild(me.scrollerContent);
		me.scroller.appendChild(me.rightResizer);

		me.canvasWrapper.appendChild(me.scroller);

		me.rangeCaption = document.createElement("div");
		me.rangeCaption.className = "rangeCaption";
		me.rangeCaption.style = "position:absolute;" + me.config.zoom.rangeCaptionCss;

		me.zoomIn = document.createElement("div");
		me.zoomIn.className = "stock-zoomer zoomIn";
		me.zoomIn.title = "Zoom in";
		me.zoomIn.style = "cursor:pointer;position:absolute;width:16px;height:16px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAKVSURBVHjajJPNSxtRFMV/mZnE6EzE0RgzpbRCECN+gbW2+AUiYoUWu2q77x9Q240761LIqmRl/oCuROJGNBuRohQsKH4E3RQDyqRUmqYj+Z68bmow1ULP7h3OOfe+d991cAt8Pl9jPp9/nE6n7wMuSZJMSZLipVLp8G+t4/pBURQ5EAi8qampedXQ0PAwn8/jcrnweDwcHR2dJhKJdWAR2L0RoOv6ndbW1sWWlpanPT09jI2NEQgE0DQNy7KIx+PEYjEikcg327bfAR8rld1ut6u7u3ttfHxcrKysiH8hk8mIUCgkdF1PAk8qAcFg8O3k5KSIRqNVhlgsJsLhsNjb26vi5+bmBPAZuCs1Nze3qKr6uq+vj+np6aoHOj4+ZmNjA9M0q/iZmRn6+/sfAS+kQqHwQNO0zpGREQByuRymaVZMjY2NZDIZkskkFxcXCCHQdZ2JiQmAXiWdTntVVaW9vR2Aw8NDwuEwqqricrmor69nc3OTtbU1/H4/s7OzqKpKW1tbweFw+CVAdjqd1NbW8j8QQgCgaVoRqFGATDabJZVKYRgGXV1dLCwsALC0tMT+/j5TU1MMDg6iKAp1dXUAnJ+f20KIH5IsywcHBwdnJycnVyPFMAwMw0CWZSzLQtM0/H4/Xq8XSZIAyjs7OzLwVSqXy3HTNGPLy8tcXl5WWrVtG5/PRzAYxOPxVF1je3s7s7q6agI7V1y32+1OhEKhqnmXSiWRy+VEuVyucKenp8WBgQETiAD69eCXTU1NF/Pz8yKVSt36E7e2tn4NDw9/Bz4BnTeWCXgOvB8aGuoZHR2lo6PDVlW1mEgksru7u3I0Gj2zLOsL8AHYuy0A4B7wDOgFDKfTqRSLxRSQ+GNaB35eiX8PACbDWDk/Ji4BAAAAAElFTkSuQmCC);";

		me.zoomOut = document.createElement("div");
		me.zoomOut.className = "stock-zoomer zoomOut";
		me.zoomOut.title = "Zoom out";
		me.zoomOut.style = "cursor:pointer;position:absolute;width:16px;height:16px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAIvSURBVHjajJO9SxthHICf+0hy5iLkmmpyRRpBRI0mDrFS8ANExA4tdLOD/0Opi7hYRyFTyWSga+e4VdciFAykkFgIhZIDxZaWXNODi7kkfbtU0aqYZ3vh93t+X7wSN9Df33+v2Ww+rtfrccAvy/KpLMuf2+12+f9Y6fJDVVVlaGjoZSAQeBEOhx81m038fj+9vb0cHR1VLcvaA3aA4jWBYRgPBgcHd6LR6NNUKsXCwgLxeJxgMIjrulQqFfb398nlct87nc4a8O6isqZp/mQy+X5xcVHs7u6K23BdV2QyGWEYxjfgyYVgdHT01fLyssjn86IbNjc3BfARGKCvry+aTqfLGxsboltqtZqYmpoSwJrqeV46FAqNz83NAXB2doZt28iyfGXbQghUVSUSiWAYBktLSxQKhUm1Xq/f13WdkZERAMrlMtlsFl3Xr0g8zyMWi7G+vo6u6wwPD3uSJMVkQPH5fPT09NANQggAQqFQCwiogNtoNLBtG9M0mZiYYHt7+9YRgsEgACcnJx0hRE1VFKVUKpWOK5XKQCKRQNM0TNO8q5E/h4eHCvAVSZIA3q6urgrHcbq6wsHBgRMOh78AK+fGpKZpViaTuTO5Wq22pqenT4EcYFxuayUSifzc2toStm3fVvn37OzsD+ADMH7tMwHPgdczMzOp+fl5xsbGOrqutyzLahSLRSWfzx87jlMA3gCfbhIAPASeAZOA6fP51FarZQPWv6Q94Nd58N8BAOqNiAGpJ1qDAAAAAElFTkSuQmCC);";

		me.canvasWrapper.appendChild(me.zoomIn);
		me.canvasWrapper.appendChild(me.zoomOut);
		me.canvasWrapper.appendChild(me.rangeCaption);

		setZoomVisible(false);
	}

	function setZoomVisible(visible) {
		if (me.config.zoom.external === true) {
			visible = false;
		}

		var displayValue = visible === true ? '' : 'none';
		me.zoomIn.style.display = displayValue;
		me.zoomOut.style.display = displayValue;
		me.rangeCaption.style.display = displayValue;
	}

	function updateResizerSize(data) {
		var design = me.config.design;

		if (!data) {
			data = me.currentDataRegion;
		}

		var ratio = me.canvasHelper.ratio;
		var start = (data.min / me.datasPerPixel + design.padding.left) / ratio;
		var end = (data.max / me.datasPerPixel + design.padding.left) / ratio;

		var contentWidthPixels = end - start;
		var borderLeftSize = 1;
		var borderRightSize = 1;

		if (contentWidthPixels < 1) {
			contentWidthPixels = 1;
		}

		if (contentWidthPixels < me.config.zoom.minScrollerWidth) {
			var resizerGrow = me.config.zoom.minScrollerWidth - contentWidthPixels;
			var half = resizerGrow / 2;

			borderLeftSize = half;
			borderRightSize = half;

			if (start - borderLeftSize < design.padding.left) {
				var delta = start - borderLeftSize - design.padding.left;
				borderLeftSize += delta;
				//borderRightSize-=delta;
			}

			var maxX = (me.totalCount / me.datasPerPixel + design.padding.left) / ratio;
			if (start + contentWidthPixels + borderRightSize > maxX) {
				var delta = start + contentWidthPixels + borderRightSize - maxX;

				borderRightSize -= delta;
			}
		}

		start -= borderLeftSize;

		me.leftResizer.style.width = borderLeftSize + "px";
		me.rightResizer.style.width = borderRightSize + "px";


		me.scroller.style.left = start + "px";
		var resizerHeight = (me.chartHelper.getPreviewSize() - 2) / ratio;
		var resizerTop = (me.config.design.padding.bottom + 1) / ratio;
		me.scroller.style.bottom = resizerTop + "px";
		me.scroller.style.height = resizerHeight + "px";

		me.scrollerContent.style.width = contentWidthPixels + "px";

		var zoomerLeft = design.padding.left / ratio + me.config.zoom.padding;
		var zoomerBottom = resizerTop + resizerHeight - 18 - me.config.zoom.padding;
		me.zoomIn.style.bottom = zoomerBottom + "px";
		me.zoomIn.style.left = zoomerLeft + "px";

		zoomerLeft += 18;
		me.zoomOut.style.bottom = zoomerBottom + "px";
		me.zoomOut.style.left = zoomerLeft + "px";

		zoomerLeft += 23;
		zoomerBottom += 2;
		me.rangeCaption.style.bottom = zoomerBottom + "px";
		me.rangeCaption.style.left = zoomerLeft + "px";
	}

	function normalizeZoomArea(newSize, resize) {
		var areaWidth = newSize.max - newSize.min;

		if (resize) {
			if (areaWidth < me.config.zoom.minimum) {
				var delta = me.config.zoom.minimum - areaWidth;
				var half = delta / 2;
				newSize.max += half;
				newSize.min -= half;
			}

			if (areaWidth > me.config.zoom.maximum) {
				var delta = areaWidth - me.config.zoom.maximum;
				var half = delta / 2;
				newSize.max -= half;
				newSize.min += half;
			}
		}

		if (newSize.min <= 0) {
			newSize.min = 0;
			newSize.max = newSize.min + areaWidth;
		}

		if (newSize.max >= me.totalCount) {
			newSize.max = me.totalCount - 1;
			newSize.min = newSize.max - areaWidth;
		}
	}

	this.updateRegion = function (min, max) {
		me.currentDataRegion.min = min;
		me.currentDataRegion.max = max;

		updateResizerSize();
	}

	function changeResizer(newSize, resize, forcePerformLoad) {
		normalizeZoomArea(newSize, resize);
		if (newSize.min == me.currentDataRegion.min && newSize.max == me.currentDataRegion.max) {
			return;
		}

		me.currentDataRegion.min = newSize.min;
		me.currentDataRegion.max = newSize.max;

		updateResizerSize();

		var performLoad = newSize.min == 0 || newSize.max >= me.totalCount - 1;
		if (performLoad || forcePerformLoad) {
			me.resizeKind = null;
			me.moving = false;
			getData(me.currentDataRegion);
		}
	}

	function handleTooltip(x, y) {

		if (!me.config.tooltipHandler) {
			return;
		}

		var details = me.chartHelper.evalTooltips(x, y);
		me.config.tooltipHandler(details);
	}

	function showCross(x, y) {

		if (x < me.chartHelper.chartLeft || x > me.chartHelper.chartRight) {
			me.crossDrawer.setVisible(false);
			return;
		}

		var group = me.chartHelper.getGroup(y, true);
		if (!group) {
			me.crossDrawer.setVisible(false);
			return;
		}

		var localGroup = me.chartHelper.getGroup(y, false);
		var drawInfo = me.chartHelper.drawInfo[localGroup];

		var tooltipInfo = me.chartHelper.tooltipManager.get(x, group);
		var xTxt = tooltipInfo.groups.length > 0 ? tooltipInfo.groups[0].dateTime : "";
		if (xTxt === '') {
			return;
		}

		var xCross = x / me.canvasHelper.ratio;
		var yCross = y / me.canvasHelper.ratio;

		var top = drawInfo.top + drawInfo.computedHeight;
		var yVal = (top - yCross * me.canvasHelper.ratio) / drawInfo.factorY + drawInfo.minMaxY.min


		me.crossDrawer.draw(xCross, yCross, me.chartHelper.chartLeft, me.chartHelper.chartRight, me.chartHelper.chartBottom, xTxt,
			me.chartHelper.YAxisRenderer.getCaption(yVal, me.config.axis.shortCaptions, drawInfo.axisesY.dformat));
	}

	function isScroller(el) {
		return el == me.scroller || el == me.scrollerContent || el == me.leftResizer || el == me.rightResizer;
	}
	
	function tryToSelectTrade(x,y){		
		var trade = me.chartHelper.selectTradeAtX(x, y);
		if (me.config.tradeSelectedHandler!=null){
			me.config.tradeSelectedHandler(!trade ? null : trade);
		}
	}

	function bindToEvents() {
		SQ4.Utils.log('Binding to events', SQ4.Consts.LOG_DEBUG);

		initResizer();

		var performLocalShift = function (newX, finishMove) {
			var delta = newX - me.localShiftX;
			delta *= me.canvasHelper.ratio;

			var points = -delta / me.chartHelper.factorX;

			var newDataRegion = {};
			newDataRegion.min = me.currentDataRegion.min + points;
			newDataRegion.max = me.currentDataRegion.max + points;

			normalizeZoomArea(newDataRegion, false);

			if (finishMove || me.dataSource.isInCache(newDataRegion.min, newDataRegion.max)) {
				changeResizer(newDataRegion, false, true);
				me.localShiftX = newX;
			} else {
				me.chartHelper.shiftContent(delta);
				updateResizerSize(newDataRegion);
			}
		}

		var mouseMoveHandler = function (evt) {
			me.mouseMoved = true;
			var zoomFactor = (window.sqAppZoom != undefined) ? window.sqAppZoom : 1;
			if (me.localShiftX != null) {
				performLocalShift(evt.screenX / zoomFactor, false);
				evt.preventDefault();
				return;
			}

			if (me.cursorCrossMode === true && evt.srcElement == me.canvas) {
				showCross(evt.offsetX * me.canvasHelper.ratio / zoomFactor, evt.offsetY * me.canvasHelper.ratio / zoomFactor);
			}

			if (me.resizeKind == null && !me.moving) {
				if (evt.srcElement == me.canvas) {
					handleTooltip(evt.offsetX * me.canvasHelper.ratio / zoomFactor, evt.offsetY * me.canvasHelper.ratio / zoomFactor);
				}
				return;
			}

			var x = evt.clientX * me.canvasHelper.ratio;
			x = x / zoomFactor;
			var deltaPixels = x - me.originalX;
			var deltaData = deltaPixels * me.datasPerPixel;

			var newDataRegion = {};
			newDataRegion.min = me.origDataRegion.min + deltaData;
			newDataRegion.max = me.origDataRegion.max + deltaData;

			changeResizer(newDataRegion, false);
			evt.preventDefault();
		};

		var scrollerMouseDownHandler = function (evt) {
			var el = evt.srcElement;
			if (isScroller(el)) {
				me.moving = true;
			} else {
				return;
			}

			me.originalX = evt.clientX * me.canvasHelper.ratio;
			me.origDataRegion.min = me.currentDataRegion.min;
			me.origDataRegion.max = me.currentDataRegion.max;
		};

		var mouseUpHandler = function (evt) {
			me.mouseDown = false;
			
			if (!me.mouseMoved){
				console.log('chart click');
				me.localShiftX = null;
				var zoomFactor = (window.sqAppZoom != undefined) ? window.sqAppZoom : 1;
				tryToSelectTrade(evt.offsetX * me.canvasHelper.ratio / zoomFactor, evt.offsetY * me.canvasHelper.ratio / zoomFactor);
				return;
			}
			
			evt.preventDefault();
			if (me.localShiftX != null) {
				performLocalShift(evt.screenX, true);
				me.localShiftX = null;
				return;
			}
			finishMoveResize();
		};

		var mouseDownHandler = function (evt) {
			me.mouseDown = true;
			me.mouseMoved = false;
			me.canvasWrapper.focus();

			var canvasSrc = evt.srcElement == me.canvas;
			var canvasLine = evt.srcElement == me.crossDrawer.horizontalLine || evt.srcElement == me.crossDrawer.verticalLine;

			var y = canvasSrc ? evt.offsetY : evt.target.offsetTop + evt.offsetY;
			y *= me.canvasHelper.ratio;

			if (y < me.chartHelper.chartBottom && (canvasSrc || canvasLine)) {
				//manual drag
				me.localShiftX = evt.screenX;
				me.crossDrawer.setVisible(false);
			}

			if (canvasSrc && y > me.chartHelper.previewTop) {
				//perform onclick scroll
				var x = evt.offsetX * me.canvasHelper.ratio;
				var newPos = x * me.datasPerPixel;

				var currentWidth = (me.currentDataRegion.max - me.currentDataRegion.min);

				var newDataRegion = {};
				newDataRegion.min = newPos - currentWidth;
				newDataRegion.max = newPos;

				changeResizer(newDataRegion, false, true);
			}

			evt.preventDefault();
		};

		var keyDownHandler = function (evt) {
			var code = evt.keyCode;
			var shift;
			var delta = Math.abs(me.currentDataRegion.max - me.currentDataRegion.min);
			if (code == 37) {
				shift = -delta;
			} else if (code == 39) {
				shift = delta;
			} else {
				return;
			}

			var newDataRegion = {};
			newDataRegion.min = me.currentDataRegion.min + shift;
			newDataRegion.max = me.currentDataRegion.max + shift;
			changeResizer(newDataRegion, false, true);
		}

		var mouseLeaveHandler = function (evt) {
			if (me.localShiftX != null) {
				performLocalShift(evt.screenX, true);
				me.localShiftX = null;
				finishMoveResize();
			}
		}

		me.canvasWrapper.addEventListener('keydown', keyDownHandler);
		me.canvasWrapper.addEventListener('mouseleave', mouseLeaveHandler);
		me.canvasWrapper.addEventListener('mousedown', mouseDownHandler);
		me.canvasWrapper.addEventListener('mousemove', mouseMoveHandler);
		me.canvasWrapper.addEventListener('mouseup', mouseUpHandler);
		me.scroller.addEventListener('mousedown', scrollerMouseDownHandler);
		me.canvasWrapper.addEventListener('wheel', function (evt) {
			var currentDelta = Math.abs(me.currentDataRegion.max - me.currentDataRegion.min);
			var scrolledSteps = evt.deltaY / 100;
			var shift = (scrolledSteps * currentDelta * me.config.zoom.screensPerOneWheel);

			var newDataRegion = {};
			newDataRegion.min = me.currentDataRegion.min + shift;
			newDataRegion.max = me.currentDataRegion.max + shift;
			changeResizer(newDataRegion, false, true);

			me.crossDrawer.setVisible(false);

			if (me.config.tooltipHandler) {
				me.config.tooltipHandler();
			}
		});

		if ((me.widthInfo && me.widthInfo.units == '%') || (me.heightInfo && me.heightInfo.units == '%')) {
			window.addEventListener('resize', function (event) {
				if (!event.isTrusted) {
					return;
				}
				me.updateSizes(true);
			});
		}

		me.zoomIn.addEventListener('click', function (evt) {
			performZoom(true);
		});

		me.zoomOut.addEventListener('click', function (evt) {
			performZoom(false);
		});

		var lineMouseMoveEventListener = function (evt) {
			var zoomFactor = (window.sqAppZoom != undefined) ? window.sqAppZoom : 1;
			var x = (evt.target.offsetLeft*me.zoom + evt.offsetX) / zoomFactor;
			var y = (evt.target.offsetTop*me.zoom + evt.offsetY) / zoomFactor;
			showCross(x * me.canvasHelper.ratio, y * me.canvasHelper.ratio);
			handleTooltip(x * me.canvasHelper.ratio, y * me.canvasHelper.ratio);
		};

		me.crossDrawer.horizontalLine.addEventListener('mousemove', lineMouseMoveEventListener);
		me.crossDrawer.verticalLine.addEventListener('mousemove', lineMouseMoveEventListener);
	}
	
	function countZoom(){
		me.zoom = 1;
		var el = me.canvasWrapper;

		while(el.tagName.toUpperCase()!='HTML'){
			//console.log(el);
			if (el.style.zoom){
				me.zoom = el.style.zoom*1;
				//console.log('zoom evaluated: '+me.zoom);
				return;
			}
			
			el = el.parentElement;
		}
		
		//console.log('no zoom found');
	}

	function updateCrossBorders() {
		me.crossDrawer.setBorders(me.chartHelper.chartLeft / me.canvasHelper.ratio,
			me.chartHelper.chartRight / me.canvasHelper.ratio,
			me.chartHelper.chartBottom / me.canvasHelper.ratio);
	}

	this.performZoomReset = function () {
		if (typeof this.initialZoom === 'undefined') {
			return;
		}

		var currentRegion = me.currentDataRegion.max - me.currentDataRegion.min;
		if (this.initialZoom === currentRegion) {
			return;
		}

		var center = me.currentDataRegion.min + currentRegion / 2;
		var halfTargetRegion = this.initialZoom / 2;

		var newDataRegion = {};
		newDataRegion.min = center - halfTargetRegion;
		newDataRegion.max = center + halfTargetRegion;

		changeResizer(newDataRegion, true, true);
	}

	this.performZoomIn = function () {
		performZoom(true);
	}

	this.performZoomOut = function () {
		performZoom(false);
	}

	function performZoom(zoomIn) {
		var newDataRegion = {};

		var currentRegion = me.currentDataRegion.max - me.currentDataRegion.min;

		var step = zoomIn === true ? currentRegion / me.config.zoom.step : currentRegion * me.config.zoom.step;
		var halfStep = step / 2;

		var center = me.currentDataRegion.min + currentRegion / 2;
		newDataRegion.min = center - halfStep;
		newDataRegion.max = center + halfStep;

		changeResizer(newDataRegion, true, true);
	}

	function finishMoveResize() {
		if (me.resizeKind != null || me.moving) {
			me.resizeKind = null;
			me.moving = false;
			getData(me.currentDataRegion);
		}
	}

	function getData(range, zoomWidth, rangeInStr) {
		if (typeof range.min === 'undefined' || typeof range.max === 'undefined') {
			return;
		}

		if (rangeInStr === true) {
			me.dataSource.load(range.min, range.max, zoomWidth, true);
		} else {
			me.dataSource.load(Math.floor(range.min), Math.floor(range.max), zoomWidth, false);
		}
	}

	function initialize() {
		SQ4.Utils.log('Initializing chart', SQ4.Consts.LOG_DEBUG);

		me.canvasWrapper.style.position = 'relative';
		initSizes();

		bindToEvents();
		me.chartHelper.clear();
	};

	function initSizes() {
		var percents = false;
		if (me.config.width) {
			me.widthInfo = SQ4.Utils.parseSize(me.config.width);
		}

		if (me.config.height) {
			me.heightInfo = SQ4.Utils.parseSize(me.config.height);
		}

		updateCanvasSize();
	}

	function updateCanvasSize() {
		countZoom();		
		me.canvasHelper.updateCanvasSize(me.canvas, me.canvasWrapper, me.widthInfo, me.heightInfo);
	}

	this.setGridVisible = function (visible) {
		me.config.axis.gridVisible = visible;
	}

	this.setGridLineColor = function (color) {
		me.config.axis.gridColor = color;
	}
	
	this.setTradesCloseTicketVisible = function (visible) {
		me.config.markers.showCloseTicket = visible;
		me.chartHelper.refresh(true);
	}
	
	this.setTradesOpenCloseValueVisible = function (visible) {
		me.config.markers.showOpenCloseValue = visible;
		me.chartHelper.refresh(true);
	}	
	
	this.setTradesClosePLVisible = function (visible) {
		me.config.markers.showClosePL = visible;
		me.chartHelper.refresh(true);
	}	

	this.setGridLineThickness = function (gridLineThickness) {
		me.config.axis.gridLineThickness = gridLineThickness;
	}

	this.setGridLinePattern = function (gridLinePattern) {
		me.config.axis.gridLinePattern = gridLinePattern;
	}

	this.updateSizes = function (ignoreCheck) {
		updateCanvasSize();
		this.chartHelper.refresh(true);

		me.evalDatasPerPixel();
		updateResizerSize();

		updateCrossBorders();

		var width = me.canvasWrapper.offsetWidth;
		if (!ignoreCheck) {
			setTimeout(function () {
				var widthAfterTimeout = me.canvasWrapper.offsetWidth;
				if (widthAfterTimeout != width) {
					me.updateSizes(true);
				}
			}, 100);
		}
	}

	this.getChartWidth = function () {
		return me.chartHelper.chartCanvasSize.width;
	}

	this.getPreviewWidth = function () {
		return me.chartHelper.getPreviewWidth();
	}

	this.setAllowedIndicators = function (indicators) {
		if (!indicators) {
			indicators = null;
		}

		me.chartHelper.setAllowedIndicators(indicators);
		me.chartHelper.refresh(true);

		me.evalDatasPerPixel();
		updateResizerSize();

		updateCrossBorders();
	}

	this.setAllowedTradeMarkers = function (tradeMarkers) {
		if (!tradeMarkers) {
			tradeMarkers = null;
		}

		me.chartHelper.setAllowedTradeMarkers(tradeMarkers);
		me.chartHelper.refresh(true);
	}

	this.evalMinMaxZoom = function () {
		this.minimumZoom = me.config.zoom.minimum;
		this.maximumZoom = me.config.zoom.maximum;

		var currentScroller = this.currentDataRegion.max - this.currentDataRegion.min;
		if (currentScroller < this.minimumZoom) {
			this.minimumZoom = currentScroller;
		} else if (currentScroller > this.maximumZoom) {
			this.maximumZoom = currentScroller;
		}
	}

	this.refresh = function () {
		me.chartHelper.refresh(true);
	}

	this.addCustomLevel = function (indicatorId, level) {
		me.chartHelper.addCustomLevel(indicatorId, level);
	}

	this.evalDatasPerPixel = function () {
		this.datasPerPixel = (this.totalCount) / (me.getPreviewWidth());
	}

	this.isCursorCrossMode = function () {
		return this.cursorCrossMode;
	}

	this.setCursorCrossMode = function (cursorCrossMode) {
		this.cursorCrossMode = cursorCrossMode;
		this.crossDrawer.setVisible(false);
	}


	this.performZoomToTradeByIndex = function(index){
		var zoomWidth = this.currentDataRegion.max - this.currentDataRegion.min;
		me.dataSource.loadByTradeIndex(this.currentDataRegion.min,this.currentDataRegion.max,zoomWidth, index);
	}

	this.zoomToNextTrade = function(){
		var index = this.chartHelper.highlightedTradeIndex;
		if (index!=null){
			if (index==this.totalTrades-1){
				return;
			}
			
			index++;			
		}else{
			index = -1;
		}
		
		this.performZoomToTradeByIndex(index);
	}
	
	this.zoomToPrevTrade = function(){
		var index = this.chartHelper.highlightedTradeIndex;
		if (index!=null){
			if (index==0){
				return;
			}
			
			index--;			
		}else{
			index = -2;
		}
		
		this.performZoomToTradeByIndex(index);		
	}
	
	this.highlightTrade = function(tradeId){
		this.chartHelper.highlightTrade(tradeId);
	}		

	this.zoomToTrade = function (tradeId, indexFrom, indexTo) {
		this.chartHelper.highlightTrade(tradeId);

		var zoomSize = this.currentDataRegion.max - this.currentDataRegion.min;

		var min = new Date(indexFrom).format("yyyy-mm-dd HH:MM:ss");
		var max = new Date(indexTo).format("yyyy-mm-dd HH:MM:ss");
		getData({ min: min, max: max }, zoomSize, true);
	}

	this.zoomToSuitableArea = function (zoomFrom, zoomTo) {
		if (this.currentDataRegion.min <= zoomFrom && zoomTo <= this.currentDataRegion.max) {
			me.chartHelper.refresh(true);
			return;
		}

		var zoomArea = zoomTo - zoomFrom;
		if (zoomArea < me.config.zoom.minimum) {
			zoomArea = me.config.zoom.minimum;
		}

		var targetArea = zoomArea * me.config.markers.zoomPart;

		var x = zoomFrom + zoomArea / 2;

		changeResizer({ min: x - targetArea, max: x + targetArea }, false, true);
	}

	function getChartDataset(id, index) {
		var drawInfo = me.chartHelper.drawInfo[id];
		var chart;
		if (drawInfo) {
			chart = drawInfo.chart;
		} else {
			for (var iId in me.chartHelper.drawInfo) {
				var info = me.chartHelper.drawInfo[iId];
				if (info.indicators && !chart) {
					for (var i = 0; i < info.indicators.length; i++) {
						var indicator = info.indicators[i];
						if (indicator.id === id) {
							chart = indicator;
						}
					}
				}
			}
		}

		if (index == null) {
			return chart;
		}

		if (chart.datasets && chart.datasets.length > index) {
			return chart.datasets[index];
		}
	}

	this.setChartAttribute = function (id, index, attribute, value) {
		this.chartHelper.setCustomAttribute(id, index, attribute, value);
	}

	this.getChartAttribute = function (id, index, attribute) {
		var chart = getChartDataset(id, index);
		if (!chart) {
			return;
		}

		return this.chartHelper.getValue(id, chart, index, attribute);
	}

	this.updateRegionCaption = function () {
		var minMax = me.chartHelper.minMaxX;
		var fromStr = new Date(minMax.min).format("dd.mm.yyyy");
		var toStr = new Date(minMax.max).format("dd.mm.yyyy");

		me.rangeCaption.innerHTML = me.config.zoom.rangePrefix + fromStr + "-" + toStr;
	}

	function callErrorHandler(message, exception) {
		if (me.config.errorHandler) {
			me.config.errorHandler(message, exception);
		}
	}

	this.switchToDark = function () {
		this.chartHelper.themeLight = false;
		this.crossDrawer.themeLight = false;
		this.chartHelper.refresh(true);
	}

	this.switchToLight = function () {
		this.chartHelper.themeLight = true;
		this.crossDrawer.themeLight = true;
		this.chartHelper.refresh(true);
	}

	this.getCurrentDataRegion = function () {
		return {
			min: Math.round(me.currentDataRegion.min),
			max: Math.round(me.currentDataRegion.max)
		};
	}

	this.setData = function (data, clearSelection) {
		try {
			this.dataSource.clear();
			this.dataSource.store(data, data.area_from, data.area_to);

			if (clearSelection){
				this.chartHelper.clearHighlightTrades();
			}
			
			this.chartHelper.clearCustomSettings();

			this.data = data;
			this.totalTrades = data.totalTrades;
			this.chartHelper.setData(data);

			this.initialZoom = data.area_to - data.area_from;

			this.currentDataRegion.min = data.area_from;
			this.currentDataRegion.max = data.area_to;

			this.totalCount = data.count;

			this.evalDatasPerPixel();
			this.evalMinMaxZoom();
			updateResizerSize();
			updateCrossBorders();

			setZoomVisible(true);

			this.updateRegionCaption();
		} catch (e) {
			SQ4.Utils.log('Error while setting data:' + e, SQ4.Consts.LOG_ERROR);
			callErrorHandler('Error while setting data', e);
			throw e;
		}
	};
};