/**
 * Statistik
 * 
 * @version 2.0.0
 * @copyright 2025 Fischer, Knoblauch & Co. Medienproduktionsges. Berlin mbH
 **/

/* global data, config, ML_APP */

var data = data || [];
var dataSets = []; // unser Daten-Model
var _ml_viewportSize = "";

config.canvas = {
	// Lieber kleinere Werte verwenden, auch wenn das automatisch eingepasst wird.
	// Es kann sonst zu Quetschungen mit der Beschriftung der Y-Achse und der Skala kommen.
	width: 100, // 500
	height: 80	// 360
};

console.log( 'config:', config );

// Chart.js - global defaults
Chart.defaults.global.elements.point.radius = 5;
Chart.defaults.global.elements.point.hoverRadius = 5;
Chart.defaults.global.elements.point.hitRadius = 10;
//Chart.defaults.global.defaultFontFamily = "'MiloWeb W04 Medium', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif";
//Chart.defaults.global.defaultFontSize = 14;

function start() {
	//console.log( 'start()' );
	var dataSets = createDataModel( data.data );
	if (dataSets.length !== 0) {
		initDropDown( config, 'chartSelectionMenu_1' );
		initDropDown( config, 'chartSelectionMenu_2' );
		//createCharts( dataSets, config );
	}
	
	$('#watchmode_btn').on('click', function( event ){
		var that = this;
		// wir benötigen ein delay, da sonst der Zustand vor der Zustandänderung abgefragt wird
		setTimeout( function() {
			var selected = $(that).hasClass('active');
			if (selected) {
				toggleWatchmode('compare');
			} else {
				toggleWatchmode('single');
			}
		}, 100);
	});
	
	//toggleWatchmode( config.watchmode );
	toggleWatchmode( config.watchmode );
	$(document).on('click', function() {
		$("#menu_content").collapse('hide');
	});
	
	$(window).on('resize', function(){
		updateWatchmodeBtn_position();
		
		var viewportSize = viewport.getChartContainerSize();
		// nur aktualisieren, wenn sich Breakpoints ändern
		if (viewportSize !== _ml_viewportSize) {
			_ml_viewportSize = viewportSize;
			//console.log( 'viewportSize:', viewportSize );
			updateHTMLLegends();
			updateChartByRWD();
		}
	});
}


function toggleWatchmode( mode ) {
	//console.log('watchmode:', mode);
	// ['watchmode_resetOnToggle']
	var watchmode_resetOnToggle = true; // default: true
	if (config && config.hasOwnProperty('watchmode_resetOnToggle')) {
		watchmode_resetOnToggle = config['watchmode_resetOnToggle'];
	}
	
	// ['watchmode_openFirstAvailableChartOnCompareMode']
	var watchmode_openFirstAvailableChartOnCompareMode = false; // default: false
	if (config && config.hasOwnProperty('watchmode_openFirstAvailableChartOnCompareMode')) {
		watchmode_openFirstAvailableChartOnCompareMode = config['watchmode_openFirstAvailableChartOnCompareMode'];
	}
	
	// ['watchmode_resetAlwaysOnSingleMode']
	var watchmode_resetAlwaysOnSingleMode = true; // default: true
	if (config && config.hasOwnProperty('watchmode_resetAlwaysOnSingleMode')) {
		watchmode_resetAlwaysOnSingleMode = config['watchmode_resetAlwaysOnSingleMode'];
	}
	
	if (mode === 'single') {
		// chart-container-parent_1
		$('#chart-container-parent_1').removeClass('col-md-6');
		$('#chart-container-parent_1').addClass('col-md-8 col-md-offset-2');
		// - enable all menu elements in dropdown
		$('#chartSelectionMenu_1 li').removeClass('disabled');
		
		// chart-container-parent_2
		$('#chart-container-parent_2').css('display', 'none'); // Chart 2 Auswahl komplett ausblenden
		
		// sternchen_info
		$('#sternchen_info').removeClass('col-md-6');
		$('#sternchen_info').addClass('col-md-8 col-md-offset-2');
		
		// 2. chart zurücksetzen
//		resetDropDown( 'chartSelectionMenu_2' );
//		$('#chart-container_2').empty();
//		// - alle chart instancen löschen im chart-container_2, die nicht mehr benötigt werden
//		for (var prop in Chart.instances) {
//			var chartInstance = Chart.instances[prop];
//			if (chartInstance.canvas.id.indexOf('canvas2_') === 0) {
//				Chart.instances[prop].destroy();
//			}
//		}
		
		// Toggle-Btn Status
		$('#watchmode_btn').removeClass('active');
		// Toggle-Btn Icon
		$($('#watchmode_btn').children().get(0)).toggle(true);
		$($('#watchmode_btn').children().get(1)).toggle(false);
		$($('#watchmode_btn').children().get(2)).toggle(true);
		$($('#watchmode_btn').children().get(3)).toggle(false);
		
		// Main-Menu
		$('#menu_content li').removeClass('active');
		$($('#menu_content li').get(0)).addClass('active');
		$("#menu_content").collapse('hide');
		
		// Tooltip
		$('#watchmode_btn').attr('title', 'Vergleichen');
		
		// ['watchmode_resetOnToggle']
		if (watchmode_resetOnToggle || watchmode_resetAlwaysOnSingleMode) {
			// Chart 1 zurücksetzen
			resetDropDown( 'chartSelectionMenu_1' );
			$('#chart-container_1').empty();
			// - alle chart instancen löschen im chart-container_1
			for (var prop in Chart.instances) {
				var chartInstance = Chart.instances[prop];
				if (chartInstance.canvas.id.indexOf('canvas1_') !== -1) {
					Chart.instances[prop].destroy();
				}
			}
			// Dummy-Charts verstecken
			$('#chart-container-dummy_1').toggle(true);
		}
		// Chart 2 zurücksetzen
		resetDropDown( 'chartSelectionMenu_2' );
		$('#chart-container_2').empty();
		// - alle chart instancen löschen im chart-container_1
		for (var prop in Chart.instances) {
			var chartInstance = Chart.instances[prop];
			if (chartInstance.canvas.id.indexOf('canvas1_2') !== -1) {
				Chart.instances[prop].destroy();
			}
		}
		$('#chart-container-dummy_2').toggle(true);
		
		// zurück nach oben scrollen
		window.scroll(0,0);
	} else { // if (mode === 'compare') { 
		// chart-container-parent_1
		$('#chart-container-parent_1').removeClass('col-md-8 col-md-offset-2');
		$('#chart-container-parent_1').addClass('col-md-6');
		
		// chart-container-parent_2
		$('#chart-container-parent_2').addClass('col-md-6');
		$('#chart-container-parent_2').css('display', 'block');
		
		// sternchen_info
		$('#sternchen_info').removeClass('col-md-8 col-md-offset-2');
		$('#sternchen_info').addClass('col-md-6');
		
		// Toggle-Btn Status
		$('#watchmode_btn').addClass('active');
		// Toggle-Btn Icon
		$($('#watchmode_btn').children().get(0)).toggle(false);
		$($('#watchmode_btn').children().get(1)).toggle(true);
		$($('#watchmode_btn').children().get(2)).toggle(false);
		$($('#watchmode_btn').children().get(3)).toggle(true);

		// falls im chartSelectionMenu_1 etwas gewählt wurde, dieses item in chartSelectionMenu_2 sperren
		var chartSelectionMenu_1_selectedIndex = parseInt($('#chartSelectionMenu_1').attr( 'data-selected_index' ));
		
		if (chartSelectionMenu_1_selectedIndex !== -1) {
			$($('#chartSelectionMenu_2 .dropdown-menu li').get( chartSelectionMenu_1_selectedIndex )).addClass('disabled');
		}
		
		// Main-Menu
		$('#menu_content li').removeClass('active');
		$($('#menu_content li').get(1)).addClass('active');
		$("#menu_content").collapse('hide');
		
		// Tooltip
		$('#watchmode_btn').attr('title', 'Zurück');
		
		// alle Drop-Downs zurücksetzen
		// ['watchmode_resetOnToggle']
		if (watchmode_resetOnToggle) {
			// Chart 1 zurücksetzen
			resetDropDown( 'chartSelectionMenu_1' );
			$('#chart-container_1').empty();
			// - alle chart instancen löschen im chart-container_1
			for (var prop in Chart.instances) {
				var chartInstance = Chart.instances[prop];
				if (chartInstance.canvas.id.indexOf('canvas1_') !== -1) {
					Chart.instances[prop].destroy();
				}
			}
			// Chart 2 zurücksetzen
			resetDropDown( 'chartSelectionMenu_2' );
			$('#chart-container_2').empty();
			// - alle chart instancen löschen im chart-container_2
			for (var prop in Chart.instances) {
				var chartInstance = Chart.instances[prop];
				if (chartInstance.canvas.id.indexOf('canvas2_') !== -1) {
					Chart.instances[prop].destroy();
				}
			}
		
			// Dummy-Charts verstecken
			$('#chart-container-dummy_1').toggle(true);
			$('#chart-container-dummy_2').toggle(true);
		}
		
		// ['watchmode_resetOnToggle']
		// ['watchmode_openFirstAvailableChartOnCompareMode']
		if (watchmode_resetOnToggle === false && watchmode_openFirstAvailableChartOnCompareMode === true) {
			// nur, wenn in chartSelectionMenu_1 etwas ausgewählt wurde!
			var chartSelectionMenu_1_hasSlectedItem = $('#chartSelectionMenu_1 li[data-selected=true]').length !== 0;
			
			if (chartSelectionMenu_1_hasSlectedItem) {
				$('#chartSelectionMenu_2 li').each( function(ind,item){
					var isDisabled = $(item).hasClass('disabled'); // Boolean
					var isSelected = $(item).attr('data-selected'); // String
					//console.log('ind:', ind, 'isDisabled:', isDisabled, 'isSelected:', isSelected  );

					if (isDisabled === false && isSelected === 'false') {
						//$($('#chartSelectionMenu_2 li').get(1)).click();
						item.click();
						return false; // break
					}
				});
			}
		}
		
		// zur Mitte scrollen, damit man beide Diagramme sieht
		var scrollToY = $('#chart-container-parent_2').offset().top - screen.height/2;
		window.scroll( 0, scrollToY );
	}
	
	updateWatchmodeBtn_position();
	
	$(document).trigger( $.Event('watchmode_changed', {target:this, currentTarget:this, watchmode:mode}) );
}

//------------------------------------------------------------------------------
//
// Data-Model
//
//------------------------------------------------------------------------------

// PapaParse liefert aus der Umwandlung von CVS2JS-Object bei der ersten Spalte immer noch
// einen Wert mehr mit, der "" (ein leerer String) ist.
// Nachtrag: 
//	   Dies rührt daher, dass eine letzte leere Zeile mit exportiert wurde.
//	   Das kann verhindert werden, wenn in der PapaParse-Konfiguration skipEmptyLines: true gesetzt wurde!
// 
// Vorgehen:
//  1. Die Daten aus PapaParse sind horizontal.
//  2. Wir bauen Column-Objekte, die Excel-Spalten abbilden, wobei eine Eigenschaft den Spalten-Titel beherbergt und eine Eigenschaft das Data-Array.
//  3. Wir ersetzen im Data-Array alle nicht gesetzten Werte durch den Wert undefined.
//     Dadurch können Kurven und Balken gerendert werden, die erst später auf der x-Achse beginnen sollen.
//  4. Wir müssen alle undefined-Werte am Ende eines Arrays entfernen, 
//     da in Spalte 1 (Excel csv2js-object) der letzte Wert nun ein undefined ist. 

/*
	Daten-Model aus csv2js-object via PapaParse (horizontal):
	
	data = [
		["Jahr",	"BIP",		"Steigerung_BIP", 	"IFO",	"Arbeitslose",	"Inflation"],	// Diagramm-Name (für jedes einzelne Diagramm)
		["1950",	"218.2",	"0.00",				"0",	"0",			"-6.4"],		// Wert 1 (für jedes einzelne Diagramm)
		["1951",	"239.3",	"9.67",				"0",	"0",			"7.6"],			// Wert 2 (für jedes einzelne Diagramm)
		...	
		[""]
	]

 */

/**
 *	Daten-Model erzeugen, aus dem die Diagramme erzeugt werden.
 *	
 *	data = [
 *		// [0] ... die x-Achse
 *		{
 *			name: "Jahr",
 *			values: ["1950", "1951", ...]
 *		},
 *		// [1] ... Diagramm 1
 *		{
 *			name: "BIP",
 *			values: ["218.2", "239.3", ...]
 *		},
 *		// [2] ... Diagramm 2
 *		{
 *			name: "Steigerung_BIP",
 *			values: ["0.00", "9.67", ...]
 *		}
 *		// ...
 *	]
 *
 * @param {Object} data	PapaParse-Objekt
 * @returns {Object}		Daten-Model
 */
function createDataModel( data ) {
	var dataSet_count,
		i,
		error_str = "Keine Daten zum Zeigen vorhanden!";
	
	if ($.isArray(data)) {
		if (!data[0]) {
			alert( error_str );
			return [];
		}
	} else {
		alert( error_str );
		return [];
	}
	
	dataSet_count = data[0].length;
	
	for (i=0; i<dataSet_count; i++) {
		// neuer Datensatz
		var obj = {name:"", values:""};
		var temp_array = _getDataset_array( data, i );
		obj.name = temp_array[0]; // Excel: 1. Zeile
		obj.values = temp_array.slice(1); // Excel: ab 2. Zeile
		_array_cleanEnd( obj.values );
		dataSets.push( obj );
	}
	console.log( 'dataSets:', dataSets );
	
	return dataSets;
}

/**
 * Bildet ein Array (mapping) aus den CVS2JS-Obj (PapaParse) aus dem dann später ein Datensatz-Objekt erzeugt wird.
 * 
 * @param {Array} data_array	Array mit den y-Werten.
 * @param {Number} dataset_num	Spalten-Nr. aus Excel (da Werte aus PapaParse horizontal)
 * @returns {Array}
 */
function _getDataset_array( data_array, dataset_num ) {
	// Variante 1:
//	var array = [];
//	for (var i = 0; i < data_array.length; i++) {
//		var value = data_array[i][dataset_num];
//		// leere Werte bekommen den Wert: undefined
//		value === "" ? value = undefined : value;
//		array.push( value );
//	}
//	return array;
	
	// Variante 2:
	return data_array.map( function(data, i, array) {
		// Korrigieren der Werte
		var value = data[dataset_num];
		// leere Werte bekommen den Wert: undefined
		//console.log( array, 'value', value );
		value === "" ? value = undefined : value;
		return value;
	});
}

//------------------------------------------------------------------------------
//
// init Drop-Down-Menus
//
//------------------------------------------------------------------------------

function chartSelectionMenu_changeHandler() {
	//console.log('CHANGE', this);
	//var selectedItem = $('#chartSelectionMenu_1 [data-selected="true"]');
	var selectedIndex = -1;
	$('#'+this.id + ' .dropdown-menu').children().each(function(ind,item){
		if ($(this).attr('data-selected') === 'true') {
			selectedIndex = ind;
		}
	});
	
	var canvasName = '';
	
	// ['watchmode_resetOnToggle']
	var watchmode_resetOnToggle = true; // default: true
	if (config && config.hasOwnProperty('watchmode_resetOnToggle')) {
		watchmode_resetOnToggle = config['watchmode_resetOnToggle'];
	}
	
	// ['watchmode_openFirstAvailableChartOnCompareMode']
	var watchmode_openFirstAvailableChartOnCompareMode = false; // default: false
	if (config && config.hasOwnProperty('watchmode_openFirstAvailableChartOnCompareMode')) {
		watchmode_openFirstAvailableChartOnCompareMode = config['watchmode_openFirstAvailableChartOnCompareMode'];
	}
	
	var watchmode = '';
	if ($('#chart-container-parent_2').css('display') === 'none') {
		watchmode = 'single';
	} else {
		watchmode = 'compare';
	}
	
	if (this.id === 'chartSelectionMenu_1') {
		$('#chartSelectionMenu_2 li').removeClass('disabled');
		$($('#chartSelectionMenu_2 li').get(selectedIndex)).addClass('disabled');
		
		canvasName = 'canvas1_'+selectedIndex;
		
		// create chart
		$('#chart-container_1').empty();
		_createChart( dataSets, config.diagrams[ selectedIndex ], canvasName, 'chart-container_1' );
		
		// chart instancen löschen, die nicht mehr benötigt werden
		for (var prop in Chart.instances) {
			var chartInstance = Chart.instances[prop];
			if (chartInstance.canvas.id.indexOf('canvas1_') === 0 && chartInstance.canvas.id !== canvasName ) {
				Chart.instances[prop].destroy();
			}
		}
		
		// Dummy-Chart verstecken
		$('#chart-container-dummy_1').toggle(false);
		
		// automatisch ein zweites Diagramm öffnen im Container: #chartSelectionMenu_2
		if (watchmode === 'compare') {
			// ['watchmode_resetOnToggle']
			// ['watchmode_openFirstAvailableChartOnCompareMode']
			if (watchmode_resetOnToggle === false && watchmode_openFirstAvailableChartOnCompareMode === true) {
				var chartSelectionMenu_1_hasSlectedItem = $('#chartSelectionMenu_1 li[data-selected=true]').length !== 0;
			
				if (chartSelectionMenu_1_hasSlectedItem) {
					$('#chartSelectionMenu_2 li').each( function(ind,item){
						var isDisabled = $(item).hasClass('disabled'); // Boolean
						var isSelected = $(item).attr('data-selected'); // String
						//console.log('ind:', ind, 'isDisabled:', isDisabled, 'isSelected:', isSelected  );

						if (isDisabled === false && isSelected === 'false') {
							item.click();
							return false; // break
						}
					});
				}
			}
		}	
	}
	
	if (this.id === 'chartSelectionMenu_2') {
		$('#chartSelectionMenu_1 li').removeClass('disabled');
		$($('#chartSelectionMenu_1 li').get(selectedIndex)).addClass('disabled');
		
		canvasName = 'canvas2_'+selectedIndex;
		
		// create chart
		$('#chart-container_2').empty();
		_createChart( dataSets, config.diagrams[ selectedIndex ], canvasName, 'chart-container_2' );
		
		// chart instancen löschen, die nicht mehr benötigt werden
		for (var prop in Chart.instances) {
			var chartInstance = Chart.instances[prop];
			if (chartInstance.canvas.id.indexOf('canvas2_') === 0 && chartInstance.canvas.id !== canvasName ) {
				Chart.instances[prop].destroy();
			}
		}
		
		// Dummy-Chart verstecken
		$('#chart-container-dummy_2').toggle(false);
		
			// automatisch ein zweites Diagramm öffnen im Container: #chartSelectionMenu_1
		if (watchmode === 'compare') {
			// ['watchmode_resetOnToggle']
			// ['watchmode_openFirstAvailableChartOnCompareMode']
			if (watchmode_resetOnToggle === false && watchmode_openFirstAvailableChartOnCompareMode === true) {
				var chartSelectionMenu_2_hasSlectedItem = $('#chartSelectionMenu_2 li[data-selected=true]').length !== 0;
			
				if (chartSelectionMenu_2_hasSlectedItem) {
					$('#chartSelectionMenu_1 li').each( function(ind,item){
						var isDisabled = $(item).hasClass('disabled'); // Boolean
						var isSelected = $(item).attr('data-selected'); // String
						//console.log('ind:', ind, 'isDisabled:', isDisabled, 'isSelected:', isSelected  );

						if (isDisabled === false && isSelected === 'false') {
							item.click();
							return false; // break
						}
					});
				}
			}
		}
	}
	
	updateHTMLLegends();
	updateChartByRWD();
}

function initDropDown( config, dropDownID ) {
	var $dropDownMenu = $('#'+dropDownID);
	var $dropDownMenuItems = $('#'+dropDownID + ' .dropdown-menu');
	
	$dropDownMenuItems.empty();
	for (var i=0; i<config.diagrams.length; i++) {
		var diagram = config.diagrams[i];
		$dropDownMenuItems.append('<li data-index="' +i+ '" data-selected="false"><a href="'+ '#' + '" class="shorten-long-text">'+ diagram.title +'</a></li>');
	}
	
	// default label-text in data-Attribut speichern
	var $dropdown_selectionLabel = $('#'+dropDownID + ' .dropdown-toggle-label');
	$dropdown_selectionLabel.attr('data-default_label_txt', $dropdown_selectionLabel.text());
	
	// Dropdown Menüs - Vergleichsmodus
	$("#"+dropDownID + " .dropdown-menu li:not(.disabled)").click(function() {
		//console.log( 'dropdown click', this );
		if ($(this).hasClass('disabled')) {
			return;
		}
		
		// update selected-Zustand
		var $dropdown_items = $(this).parent().children(); // li-Elemente
		$dropdown_items.attr('data-selected', 'false');
		$dropdown_items.removeClass('active');
		
		$(this).attr('data-selected', 'true');
		$(this).addClass('active');
		
		// update Label
		var $dropdown_selectionLabel = $(this).parent().siblings('.dropdown-toggle').find('.dropdown-toggle-label');
		//console.log('dropdown_selectionLabel', $dropdown_selectionLabel);
		$dropdown_selectionLabel.text( $(this).text() );
		$dropdown_selectionLabel.attr( 'title', $(this).text() );
		
		// change-Event feuern
		var $dropdown = $(this).parent().parent();
		var oldSelectedIndex = $dropdown.attr( 'data-selected_index' );
		if (oldSelectedIndex !== $(this).attr('data-index')) {
			$dropdown.attr( 'data-selected_index', $(this).attr('data-index') );
			// Event feuern
			$($dropdown).trigger( $.Event('change', {target:this, currentTarget:this}) );
		}
	});
	
	$dropDownMenu.on('change', chartSelectionMenu_changeHandler);
}

function resetDropDown( dropDownID ) {
	var $dropdown = $("#"+dropDownID);
	var $dropdown_items = $($dropdown).find('.dropdown-menu').children();
	var $dropdown_selectionLabel = $('#'+dropDownID + ' .dropdown-toggle-label');
	//console.log( '$dropdown:', $dropdown, '$dropdown_items:', $dropdown_items, '$dropdown_selectionLabel:', $dropdown_selectionLabel );
	
	$dropdown.attr('data-selected_index', '-1');
	$dropdown_items.removeClass('disabled active') 
				   .attr('data-selected', 'false');
	$dropdown_selectionLabel.text( $dropdown_selectionLabel.attr('data-default_label_txt') );
	$dropdown_selectionLabel.attr( 'title', $dropdown_selectionLabel.attr('data-default_label_txt') );
}

//------------------------------------------------------------------------------
//
// Build Chart
//
//------------------------------------------------------------------------------

function createCharts( dataSets, config ) {
	for (var i=0; i<config.diagrams.length; i++) {
		_createChart( dataSets, config.diagrams[i], 'canvas' + i, 'chart-container_1' );
	}
}

function _createChart( dataSets, configDiagramObj, chartID, targetContainerID ) {
	//console.log( 'configDiagramObj:', configDiagramObj );
	
	// Defaults setzen: configDiagramObj
	configDiagramObj['ml_tooltip_legendTitlePosition']	= configDiagramObj['ml_tooltip_legendTitlePosition'] || 'none';
	configDiagramObj['ml_tooltip_legendTitleSuffix']	= configDiagramObj['ml_tooltip_legendTitleSuffix'] || '<br>';
	configDiagramObj['ml_tooltip_valueSuffix']			= configDiagramObj['ml_tooltip_valueSuffix'] || '';
	configDiagramObj['ml_tooltip_widthScaleFactor_rwd'] = configDiagramObj['ml_tooltip_widthScaleFactor_rwd'] || 0.5;
	
	// Legend-clickHandler
	var defaultLegendClickHandler = Chart.defaults.global.legend.onClick;
	
	// alle Y-Values in ein Array stecken zur Bestimmung von min/max
	var _concatedYValues_array = [];
	for (var a=0; a<configDiagramObj.charts.length; a++) {	
		try {
			var _yValues_strArray = dataSets[ configDiagramObj.charts[a].column_yValues ].values; // ["6", "9", "3", ...]
		} catch(e) {
			console.error('Der Wert "column_yValues: ' +configDiagramObj.charts[a].column_yValues+ '" (Spalten-Nr. in Excel) ist ungültig (max-Wert: ' +(dataSets.length-1)+ ')!', '\nconfigDiagramObj:', configDiagramObj );
		}
		// Prüfen, ob alle Werte valide sind (Zahlen sind)
		_concatedYValues_array = _concatedYValues_array.concat( _yValues_strArray );
		//console.log( 'chart ' + a + ':', _yValues_strArray );
	}
	_concatedYValues_array.forEach(function(element, index){
		if ( isNaN(element) ) {
			_concatedYValues_array[index] = "";
		}
	});
	//console.log('---------- Max:',_concatedYValues_array, Math.max.apply(Math, _concatedYValues_array));
	
	var _chart = {
		type: 'bar', // muss 'bar' sein, für "mixed chart types"
		data: {
			labels: [],
			datasets: [],
			options: {}
		},
		options: {
			title: {},
			layout: {
				padding: {
					left: 5,
					right: 5,
					top: 10,
					bottom: 10
				}
			},
			scales: {
				xAxes: [{
					display: true,
					scaleLabel: {
						display: true,
						labelString: '',
						fontColor: 'rgb(0, 68, 138)',
						fontStyle: config['ml_label_fontStyle'] !== undefined ? config['ml_label_fontStyle'] : 'normal', // @since: 1.4.0,
						fontSize: config['ml_label_fontSize'] !== undefined ? config['ml_label_fontSize'] : undefined // @since: 1.4.0
					},
					ticks: {
						stepSize: 1,
						fontColor: 'rgb(0, 68, 138)',
						autoSkip: configDiagramObj['ml_xAxis_autoSkip'] !== undefined ? configDiagramObj['ml_xAxis_autoSkip'] : true // @since: 1.4.0
					},
					gridLines: {
						// offsetGridLines:false => die Labels der x-Achse befinden sich auf den vertikalen Hilfslinien
						// offsetGridLines:true  => die Labels der x-Achse befinden sich zwischen den vertikalen Hilfslinien,
						//							macht nur Sinn bei Balken-Diagrammen,
						//							Die Hilfslinien werden auch als Kategorie-Begrenzung genutzt, um mehrere Balken in einer Kategorie darzustellen
						offsetGridLines: false,
						color: 'rgb(87, 185, 226)',
						zeroLineColor: 'rgb(87, 185, 226)'
					},
					maxBarThickness: configDiagramObj['maxBarThickness'] !== undefined ? configDiagramObj['maxBarThickness'] : undefined, // @since: 1.5.0
					stacked: configDiagramObj['ml_stackedBarChart'] !== undefined ? configDiagramObj['ml_stackedBarChart'] : false // @since: 1.6.0
				}],
				yAxes: [{
					display: true,
					scaleLabel: {
						display: true,
						labelString: '',
						fontColor: 'rgb(0, 68, 138)',
						fontStyle: config['ml_label_fontStyle'] !== undefined ? config['ml_label_fontStyle'] : 'normal', // @since: 1.4.0,
						fontSize: config['ml_label_fontSize'] !== undefined ? config['ml_label_fontSize'] : undefined // @since: 1.4.0
					},
					ticks: {
						fontColor: 'rgb(0, 68, 138)',
						callback: function(value, index, values) {
							value = value * (configDiagramObj['unitScaleFactor'] || 1);
							var returnValue = Number(value).toFixed( configDiagramObj['yAxis_numberOfDigitsAfterDecimalPoint'] || 0 );
							returnValue	= returnValue.toString().replace(".", ",");
							return ML_APP.CLASSES.Math.NumberThousandsFormatter.format(returnValue, '.', 3);
						},
						max: configDiagramObj['ml_yAxis_maxValue'] !== undefined ? configDiagramObj['ml_yAxis_maxValue'] : undefined, // @since: 1.4.0
						ml_suggestedMax: Math.max.apply(Math, _concatedYValues_array),
						ml_suggestedMin: Math.min.apply(Math, _concatedYValues_array),
						ml_yAxis_dynamicScaling: configDiagramObj['ml_yAxis_dynamicScaling']
					},
					gridLines: {
						color: 'rgb(87, 185, 226)',
						zeroLineColor: 'rgb(87, 185, 226)'
					},
					stacked: configDiagramObj['ml_stackedBarChart'] !== undefined ? configDiagramObj['ml_stackedBarChart'] : false // @since: 1.6.0
				}]
			},
			legend: {
				ml_numberOfColumns: configDiagramObj['ml_htmlLegend_numberOfColumns'] || 1,
				ml_htmlLegend_numberOfColumns_rwd: configDiagramObj['ml_htmlLegend_numberOfColumns_rwd'] || null,
				onClick: newLegendClickHandler,
				labels: {
					usePointStyle: true // statt Rechtecke werden Kreise gezeichnet
				},
				position: "top",
				display: false // muss false, wenn HTML-Legende verwendet wird
			}
			// tooltips // siehe unten
		}
	};
	
	// Haupttitel
	if (configDiagramObj['title'] !== '') {
		_chart.options.title.display = false;
		_chart.options.title.text = configDiagramObj['title'] || '';
	}
	
	// Labels x-Achse
	_chart.data.labels = dataSets[ configDiagramObj['column_xAxis'] ].values;
	//console.log("chart.data.labels:", chart.data.labels);
	
	// Beschriftung x- und y-Achse
	if (configDiagramObj['label_xAxis'] && configDiagramObj['label_xAxis'] !== '') {
		_chart.options.scales.xAxes[0].scaleLabel.labelString = configDiagramObj['label_xAxis'];
	}
	if (configDiagramObj['label_yAxis'] && configDiagramObj['label_yAxis'] !== '') {
		_chart.options.scales.yAxes[0].scaleLabel.labelString = configDiagramObj['label_yAxis'];
	}
	
	// Tooltips
	_chart.options.tooltips = {
		enabled: false, // betrifft nur die canvas internen (wir benutzen HTML-Tooltips)
		position: 'nearest',
		mode: configDiagramObj['ml_tooltip_mode'] || 'nearest', // 'index': alle y-Werte aller sichtbaren datasets
		callbacks: {
			// HTML-Tooltips
			label: function(tooltipItems, data) {
				//console.log('tooltipItems:', tooltipItems, '\ndata:', data );
				// format: 
				//   Quelle: http://009co.com/?p=598
				//var value_str = data.datasets[tooltipItems.datasetIndex].label +': ' + tooltipItems.yLabel;
				//var value_str = data.datasets[tooltipItems.datasetIndex].label +': ' + ML_APP.CLASSES.Math.NumberThousandsFormatter.format(tooltipItems.yLabel.toString().replace(".", ","), '.', 3);
				var value = tooltipItems.yLabel * (configDiagramObj['unitScaleFactor'] || 1);
				value = value.toFixed( configDiagramObj['ml_tooltip_numberOfDigitsAfterDecimalPoint'] || 0 );
				var value_str = ML_APP.CLASSES.Math.NumberThousandsFormatter.format(value.toString().replace(".", ","), '.', 3);
				if (configDiagramObj['ml_tooltip_valueSuffix'] !== '') {
					value_str += configDiagramObj['ml_tooltip_valueSuffix'];
				}
				
				// ['ml_tooltip_legendTitlePosition']
				// ['ml_tooltip_legendTitleSuffix']
				var legendTitle = '';
				if (configDiagramObj['ml_tooltip_legendTitlePosition'] === 'body') {
					legendTitle = data.datasets[tooltipItems.datasetIndex].label + configDiagramObj['ml_tooltip_legendTitleSuffix'];
				}
				
				if (data.datasets[tooltipItems.datasetIndex].data_tooltips) {
					var tooltipCustomText = data.datasets[tooltipItems.datasetIndex].data_tooltips[tooltipItems.index];
					var tooltipCustomMarkup = '';
					if (tooltipCustomText !== undefined) {
						tooltipCustomMarkup = '<br>' + '<span class="chartjs-tooltip-custom-text">' + tooltipCustomText + '</span>';
					}
					
					if (_chart.options.tooltips.mode === 'nearest') {
						// 'nearest': nur den aktuellen y-Wert bezogen auf den x-Wert
						return legendTitle + '<span class="chartjs-tooltip-value">' + value_str + '</span>' + tooltipCustomMarkup; // wenn Zusatz Info-Text
					} else if (_chart.options.tooltips.mode === 'index') {
						// 'index': alle y-Werte aller sichtbaren datasets bezogen auf den x-Wert
						// 1. Teilstring: Wert
						var return_str = legendTitle + '<span class="chartjs-tooltip-value">' + value_str + '</span>';
						// 2. Teilstring: Tooltip (aber eben erst anzeigen nach dem letzten sichtbar geschalteten Datensatz) 
						// map_prop_hidden = [true, false, false, true, false]; // die hidden-property jedes Datensatzes
						var map_prop_hidden = data.datasets.map( function(obj){return obj.hidden;} );
						var last_visible_datasetIndex = map_prop_hidden.lastIndexOf(false);
						if (tooltipItems.datasetIndex === last_visible_datasetIndex) {
							return_str += tooltipCustomMarkup; // wenn Zusatz Info-Text
						}
						return return_str;
					}
				} else {
					return legendTitle + '<span class="chartjs-tooltip-value">' + value_str + '</span>';
				}
			}
		},
		// weiterreichen des spezifischen configObjektes ... (defaults wurden oben gesetzt)
		//   - wird verwendet in Chart.defaults.global.tooltips.custom
		ml_configDiagramObj: configDiagramObj
	};
	
	// chart bzw. mehrere charts erstellen (wenn mixed charts)
	var charts_array = configDiagramObj['charts'];
	//console.log('charts_array', charts_array);
	
	function newLegendClickHandler(e, legendItem) {
		//console.log( 'this:', this );
		//console.log( 'e:', e, 'legendItem:', legendItem );
		var _datasets = this.chart.config.data.datasets;
		
		defaultLegendClickHandler.call(this, e, legendItem);
		
		// X-Achsen Einteilungspositionierung einstellen (offsetGridLines)
		fixTicksAtBarCharts( this.chart );
	};
	
	function createDataset() {
		charts_array.forEach( function(configChartObj, index, array){
			// ... ein Graph bzw. eine chart
			var dataset = {};
			
			// unsere defaults
			dataset.id = 'chart' + configChartObj['column_yValues']; // chart + <Spalten-Nr>
			dataset.label = dataSets[ configChartObj['column_yValues'] ].name; // Excel: 1. Zeile
			dataset.data = dataSets[ configChartObj['column_yValues'] ].values; // Excel: ab 2. Zeile
			if (configChartObj['column_tooltips']) {
				dataset.data_tooltips = dataSets[ configChartObj['column_tooltips'] ].values;
				//console.log('dataset.data_tooltips:', dataset.data_tooltips);
			}
			dataset.backgroundColor = config['colors'][index]['backgroundColor'];
			dataset.borderColor = config['colors'][index]['borderColor'];
			dataset.borderWidth = 1;
			dataset.fill = false; // gilt nicht für type "bar"

			// merged aus configChartObj
			// ... so sind wir offen für alle Properties
			for (var key in configChartObj) {
				//console.log( 'key:', key, ', value:', configChartObj[key] );
				dataset[key] = configChartObj[key];
			}

			_chart.data.datasets.push( dataset );
		});
	}
	createDataset();
	
	function updateData() {
		// clipped range (min, max)
		var clipped_range = configDiagramObj._slider.slider('getValue'); // returns [min,max]
		var clipped_range_min = clipped_range[0];
		var clipped_range_max = clipped_range[1];
		
		// update x-Achse
		var full_xAxis_array = dataSets[ configDiagramObj['column_xAxis'] ].values;
		var clipped_xAxis_values = full_xAxis_array.slice( clipped_range_min, clipped_range_max );
		_chart.data.labels = clipped_xAxis_values;
		
		// update dataset
		charts_array.forEach( function(configChartObj, index, array){
			// ... ein Graph bzw. eine chart
			var dataset = _chart.data.datasets[index];
			//console.log( 'dataset:', dataset );
			
			// yValues
			var yValues = dataSets[ configChartObj['column_yValues'] ].values; // Excel: ab 2. Zeile
			dataset.data = yValues.slice( clipped_range_min, clipped_range_max );
			
			// data_tooltips
			if (configChartObj['column_tooltips']) {
				var data_tooltips = dataSets[ configChartObj['column_tooltips'] ].values;
				dataset.data_tooltips = data_tooltips.slice( clipped_range_min, clipped_range_max );
			}
		});
	}
	
	// Chart dem document hinzufügen
	$('#'+targetContainerID).append( $('<canvas id="'+ chartID +'" width="'+ config.canvas.width +'" height="'+ config.canvas.height +'" style="max-width:720px;"></canvas>') );
	var ctx2 = document.getElementById( chartID );
	var myChart = new Chart(ctx2, _chart);
	
	applySuggestedMinMaxValues( myChart );
	
	// Legende dem document hinzufügen
	$('#'+chartID).before( $('<div id="' + 'legend_' + chartID + '" class="ml-legend-container"></div>') );
	document.getElementById( 'legend_' + chartID ).innerHTML = myChart.generateLegend();
	legendUpdateVisibleStates( 'legend_' + chartID, myChart );
	myChart.ml_htmlLegendID = 'legend_' + chartID; // brauchen wir für später, zum aktualisieren RWD
	
	//--------------------------------------------------------------------------
	// Range Slider (Bootstrap-Slider)
	// 
	// https://github.com/seiyria/bootstrap-slider
	//--------------------------------------------------------------------------
	$('#'+targetContainerID).append( $('<input id="' +chartID+'_range' + '" type="text" value=""/>') );
	configDiagramObj._slider = $('#'+chartID+'_range').slider({
		id: chartID+'_range_slider',
		min: 0,
		max: dataSets[ configDiagramObj['column_xAxis'] ].values.length,
		value: [0, dataSets[ configDiagramObj['column_xAxis'] ].values.length],
		step: 1,
		tooltip: 'hide'
	});
	$('#'+chartID+'_range').on( 'change', function(event){
		// update datasets
		updateData();
		// update chart
		myChart.update();
		
		forceCanvasRedrawOnOldDevices();
	} );
	
	// Range Slider zeigen/verstecken
	if (configDiagramObj['rangeSlider_hidden'] === true) {
		$($(configDiagramObj._slider.slider('getElement'))).css('display', 'none');
		//configDiagramObj._slider.slider('disable');
	}
	
	forceCanvasRedrawOnOldDevices();
}

/**
 * Entfernt alle Werte am Ende die undefined sind (undefined-Werte dürfen nicht das Array abschließen).
 * Dies ist vor allem für die Beschriftung der x-Achse von Bedeutung.
 * 
 * @param {Array} array
 * @returns {undefined}
 */
function _array_cleanEnd( array ) {
	while( array[array.length-1] === undefined ) {
		array.pop();
	}
}

//------------------------------------------------------------------------------
//
// Custom Tooltips (HTML) (siehe Sample "custom-pie.html")
//
//------------------------------------------------------------------------------
Chart.defaults.global.tooltips.enabled = false; // canvas tooltips deaktivieren
Chart.defaults.global.tooltips.cornerRadius = 10; // siehe Button-Radius
Chart.defaults.global.tooltips.custom = function(tooltip) {
	// Tooltip Element
	var tooltipEl = document.getElementById('chartjs-tooltip');

	// Hide if no tooltip
	if (tooltip.opacity === 0) {
		tooltipEl.style.opacity = 0;
		return;
	}

	// Set caret Position
	tooltipEl.classList.remove('above', 'below', 'no-transform');
	if (tooltip.yAlign) {
		tooltipEl.classList.add(tooltip.yAlign);
	} else {
		tooltipEl.classList.add('no-transform');
	}

	function getBody(bodyItem) {
		return bodyItem.lines;
	}

	// Set Text
	if (tooltip.body) {
		var that = this; // chart-instanz
		var titleLines = tooltip.title || [];
		var bodyLines = tooltip.body.map(getBody);

		var innerHtml = '<thead>';
		//console.log( 'tooltip:', tooltip );
		//console.log( 'tooltip this:', this );
		//console.log('that._options:', that._options);
		
		// :fix: 
		// Aufgrund der Möglichkeit, in den Labels der x- und y-Achse Umbrüche zu definieren via "\n" (siehe plugin),
		// kommt jede Zeile als separates item in dem titleLines-Array.
		// Wir fixen das, indem wir wieder ein Array mit nur einem Title Element daraus machen. 
		titleLines = [titleLines.join('')];
		
		titleLines.forEach(function(title) {
			innerHtml += '<tr><th>';
			
			// [ml_tooltip_legendTitlePosition]
			if (that._options['ml_configDiagramObj']['ml_tooltip_legendTitlePosition'] === 'head') {
				bodyLines.forEach(function(body, i) {
					var legendItem = that._chart.chart.legend.legendItems[ tooltip.dataPoints[i].datasetIndex ];
					innerHtml += legendItem.text + that._options['ml_configDiagramObj']['ml_tooltip_legendTitleSuffix'];
				});
			}
			
			innerHtml += title;
			innerHtml += '</th></tr>';
		});
		innerHtml += '</thead><tbody>';
		
		// ['ml_tooltip_customTextPosition']
		if (that._options['ml_configDiagramObj']['ml_tooltip_customTextPosition'] === 'head') {
			// Custom-Tooltip-Text umpositionieren
			var customTooltipText = '';
			var customTooltipText_regex = /<span class=\"chartjs-tooltip-custom-text\".*?<\/span>/gmi;
			tooltip.body.forEach(function(body, i) {
				body.lines.forEach(function(line, j, lines){
					var searchResult = customTooltipText_regex.exec(line);
					line = line.replace(customTooltipText_regex, '');
					lines[j] = line; // neu zuweisen
					//console.log('searchResult:', searchResult);
					if (searchResult !== null) {
						customTooltipText = searchResult[0];
					}
				});
			});
			if (customTooltipText !== '') {
				innerHtml += '<tr><td>'+ customTooltipText +'</td></tr>';
			}
		}
		
		bodyLines.forEach(function(body, i) {
			var colors = tooltip.labelColors[i];
			
			var legendItem = that._chart.chart.legend.legendItems[ tooltip.dataPoints[i].datasetIndex ];
			//console.log( 'legendItem', legendItem );
			
			var style_bg = 'background: rgb(255, 255, 255)';
			style_bg += '; border-color: rgb(255, 255, 255)';
			style_bg += '; border-width: ' + legendItem.lineWidth + 'px';
			style_bg += '; border-style: ' + (legendItem.lineDash === undefined ? 'solid' : 'dotted');
			style_bg += '; position: absolute';
			style_bg += '; z-index: 1';
			var span_bg = '<span class="chartjs-tooltip-key" style="' + style_bg + '"></span>';
			
			var style = 'background:' + colors.backgroundColor;
			style += '; border-color:' + colors.borderColor;
			style += '; border-width: ' + legendItem.lineWidth + 'px';
			style += '; border-style: ' + (legendItem.lineDash === undefined ? 'solid' : 'dotted');
			style += '; position: absolute';
			style += '; z-index: 2';
			var span = '<span class="chartjs-tooltip-key" style="' + style + '"></span>';
			
			innerHtml += '<tr><td>' + span_bg + span + '<div class="chartjs-tooltip-text">' + body + '</div>' + '</td></tr>';
		});
		innerHtml += '</tbody>';

		var tableRoot = tooltipEl.querySelector('table');
		tableRoot.innerHTML = innerHtml;
	}
	
	/*:hack:*/
	var ml_layoutOffset_top = 0;
	try {
		//ml_layoutOffset_top = $('#chart-container_1').offset().top;
		//ml_layoutOffset_top = $('#'+this._chart.canvas.parentElement.id).offset().top;
		ml_layoutOffset_top = parseInt( $('body').css('padding-top') );
	} catch(e) {}
	
	var ml_layoutOffset_left = 0;
	try {
		//var ml_layoutOffset_padding_left = parseInt( $(this._chart.canvas.parentNode.parentNode).css( 'padding-left' ) );
		//ml_layoutOffset_left = $('#'+this._chart.canvas.parentElement.id).offset().left - ml_layoutOffset_padding_left;
		ml_layoutOffset_left = $('#'+this._chart.canvas.parentElement.id).offset().left - this._chart.canvas.parentElement.offsetLeft;
	} catch(e) {}
	
	
	var canvasClientRect = this._chart.canvas.getBoundingClientRect();
	//var positionY = this._chart.canvas.offsetTop + ml_layoutOffset_top;
	//var positionX = this._chart.canvas.offsetLeft + ml_layoutOffset_left;
	
	var positionY = $(this._chart.canvas).offset().top;
	var positionX = $(this._chart.canvas).offset().left;
	
	//console.log( 'this._chart.canvas.offsetTop:', this._chart.canvas.offsetTop, 'this._chart:', this._chart );
	
	//console.log('tooltip:', tooltip, 'this', this);
	
	// Display, position, and set styles for font
	
	// ['ml_tooltip_widthScaleFactor_rwd']
	var viewportSize = viewport.getChartContainerSize();
	var ml_tooltip_widthScaleFactor_rwd = 0.5; // default
	try {
		if (that._options['ml_configDiagramObj']['ml_tooltip_widthScaleFactor_rwd']) {
			ml_tooltip_widthScaleFactor_rwd = that._options['ml_configDiagramObj']['ml_tooltip_widthScaleFactor_rwd'][viewportSize];
		}
	} catch(e) {}
	//console.log( 'ml_tooltip_widthScaleFactor_rwd:', ml_tooltip_widthScaleFactor_rwd );
	
	tooltipEl.style.width = (canvasClientRect.width * ml_tooltip_widthScaleFactor_rwd) + 'px';
	
	// Tooltip-Sprechblase
	// alle Ecken sind abgerundet, nur die Ecke am Punkt ist Spitz (radius=0)
	var corner = {
		top: true,
		left: true
	};
	
	tooltipEl.style.opacity = 1;
	//tooltipEl.style.left = positionX + tooltip.caretX + 'px';
	tooltipEl.style.left = positionX + tooltip.caretX + (parseFloat(tooltipEl.style.width)/2) + 'px'; // links vom Punkt
	// rechts vom Punkt
	if (tooltip.caretX > canvasClientRect.width/2) {
		tooltipEl.style.left = positionX + tooltip.caretX - (parseFloat(tooltipEl.style.width)/2) + 'px';
		corner.left = false;
	}
	//tooltipEl.style.top = positionY + tooltip.caretY + 'px';
	tooltipEl.style.top = positionY + tooltip.caretY + 'px'; // unterhalb vom Punkt
	if (tooltip.caretY > canvasClientRect.height/2) {
		//console.log( 'tooltipEl', $(tooltipEl).height() );
		tooltipEl.style.top = positionY + tooltip.caretY - $(tooltipEl).height() - tooltip.yPadding*2 + 'px'; // oberhalb vom Punkt
		corner.top = false;
	}
	
	var _radius = tooltip.cornerRadius + "px";
	var borderRadius = _radius;
	if (corner.top && corner.left) {
		borderRadius = "0px" + ' ' + _radius + ' ' + _radius + ' ' + _radius;
	} else if (corner.top && !corner.left) {
		borderRadius = _radius + ' ' + '0px' + ' ' + _radius + ' ' + _radius;
	} else if (!corner.top && !corner.left) {
		borderRadius = _radius + ' ' + _radius + ' ' + '0px' + ' ' + _radius;
	} else if (!corner.top && corner.left) {
		borderRadius = _radius + ' ' + _radius + ' ' + _radius + ' ' + '0px';
	}
	tooltipEl.style.borderRadius = borderRadius;
	
	tooltipEl.style.fontFamily = tooltip._bodyFontFamily; // korrigiert
	tooltipEl.style.fontSize = tooltip.fontSize;
	tooltipEl.style.fontStyle = tooltip._fontStyle;
	tooltipEl.style.padding = tooltip.yPadding + 'px ' + tooltip.xPadding + 'px';
};

// Define a plugin to provide data labels
Chart.plugins.register({
	afterDatasetsDraw: function(chart, easing) {
		// To only draw at the end of animation, check for easing === 1
		var ctx = chart.ctx;

		chart.data.datasets.forEach(function (dataset, i) {
			var meta = chart.getDatasetMeta(i);
			if (!meta.hidden) {
				meta.data.forEach(function(element, index) {
					// Draw the text in black, with the specified font
					ctx.fillStyle = 'rgb(0, 0, 0)';

					var fontSize = 16;
					var fontStyle = 'normal';
					var fontFamily = 'Helvetica Neue, Arial';
					ctx.font = Chart.helpers.fontString(fontSize, fontStyle, fontFamily);

					// Just naively convert to string for now
					var dataString = "";
					if (dataset.data_tooltips) {
						// wenn kein Extra-Tooltip sein soll, muss im Excel-Feld ein Leerzeichen " " angegeben werden!
						if (dataset.data_tooltips[index] !== " ") {
							//dataString = "i"; // Bug, i steht in Balkendiagramm auch am Fuß des Balkens
						}
					}
					//var dataString = dataset.data[index].toString() || "";
					//var dataString = dataset.ml_text[index].toString();
					//console.log('dataString', dataString);
					// Make sure alignment settings are correct
					ctx.textAlign = 'center';
					ctx.textBaseline = 'middle';

					var padding = 5;
					var position = element.tooltipPosition();
					ctx.fillText(dataString, position.x, position.y - (fontSize / 2) - padding);
				});
			}
		});
	}
});

/**
 * Umbrüche in Data-Labels via "\n" realisieren.
 * 
 * Quelle: https://stackoverflow.com/questions/37090625/chartjs-new-lines-n-in-x-axis-labels-or-displaying-more-information-around-ch
 * @param {type} chart
 * @returns {String}
 */
Chart.plugins.register({
	beforeInit: function (chart) {
		chart.data.labels.forEach(function (e, i, a) {
			if (/\n/.test(e)) {
				a[i] = e.split(/\n/);
			}
		});
	}
});

//------------------------------------------------------------------------------
// 
// HTML-Legende 
//
//------------------------------------------------------------------------------

// Quelle: siehe https://stackoverflow.com/questions/39517956/onclick-event-to-hide-dataset-chart-js-v2
/*
Chart.defaults.global.legendCallback = function(chart) {
	//console.log('legendCallback: chart:', chart);
	var legendHtml = [];
	legendHtml.push('<table>');
	legendHtml.push('<tr>');
	for (var i=0; i<chart.data.datasets.length; i++) {
		legendHtml.push('<td><div class="chart-legend" style="background-color:' + chart.data.datasets[i].backgroundColor + '">&nbsp;&nbsp;</div></td>');
		if (chart.data.datasets[i].label) {
			legendHtml.push('<td class="chart-legend-label-text" onclick="updateDataset(event, ' + '\'' + chart.legend.legendItems[i].datasetIndex + '\'' + ',this,' + chart.id.toString() +')">' + chart.data.datasets[i].label + '</td>');
		}
	}
	legendHtml.push('</tr>');
	legendHtml.push('</table>');
	return legendHtml.join("");
};*/

/*
<div class="ml-legend-container">
	<ul class="ml-legend">
		<li>
			<div class="ml-legend-shape"></div>
			<div class="ml-legend-shape-bg-bridge"></div>
			<div class="ml-legend-shape-bg"></div>
			<span class="ml-legend-label">erster</span>
		</li>
		<li class="ml-legend-item-active">
			<div class="ml-legend-shape"></div>
			<div class="ml-legend-shape-bg-bridge"></div>
			<div class="ml-legend-shape-bg"></div>
			<span class="ml-legend-label">zweiter sehr langer Titel um den Umbruch zu testen</span>
		</li>
		<li>
			<div class="ml-legend-shape"></div>
			<div class="ml-legend-shape-bg-bridge"></div>
			<div class="ml-legend-shape-bg"></div>
			<span class="ml-legend-label">dritter</span>
		</li>
	</ul>
</div>
*/

Chart.defaults.global.legendCallback = function(chart) {
	//console.log('legendCallback: chart:', chart);
	var legendHtml = [];
	if (chart.data.datasets.length === 1) {
		legendHtml.push('<ul class="ml-legend" style="display:none;">');
	} else {
		legendHtml.push('<ul class="ml-legend">');
	}
	
	var ml_numberOfColumns = chart.options.legend.ml_numberOfColumns;
	
	for (var i=0; i<chart.data.datasets.length; i++) {
		//console.log( 'chart.data.datasets[i]', chart.data.datasets[i] );
		
		//style_bg += '; border-style: ' + (chart.data.datasets[i].lineDash === undefined ? 'solid' : 'dotted');
		
		legendHtml.push('<li onclick="updateDataset(event, ' + '\'' + chart.legend.legendItems[i].datasetIndex + '\'' + ',this,' + chart.id.toString() +')">');
		legendHtml.push('<div class="ml-legend-shape" style="background-color:' + chart.data.datasets[i].backgroundColor + '; border-color:' + chart.data.datasets[i].borderColor + '; border-style:' + (chart.data.datasets[i].borderDash === undefined ? 'solid' : 'dotted') + ';' + '"></div>');
		legendHtml.push('<div class="ml-legend-shape-bg-bridge"></div>'); // Übergang, Sprechblase like
		legendHtml.push('<div class="ml-legend-shape-bg"></div>'); // weißer Hintergrund, falls Farben transparent sind
		if (chart.data.datasets[i].label) {
			//legendHtml.push('<span class="ml-legend-label">' + chart.data.datasets[i].label + '</span>');
			legendHtml.push('<div class="ml-legend-label">' + chart.data.datasets[i].label + '</div>');
		}
		legendHtml.push('</li>');
		
		// Die Einstellungen für RWD haben Vorrang!
		var _numberOfColumns_rdw = getHTMLLegend_numberOfColumns_rwd( chart );
		if (!isNaN(_numberOfColumns_rdw)) {
			ml_numberOfColumns = _numberOfColumns_rdw;
		}
		
		//console.log('ml_numberOfColumns:', ml_numberOfColumns);
		
		if (ml_numberOfColumns === 2) {
			// nach jedem 2. Element ein Trenn-Element einfügen
			if (i%2 === 1) {
				legendHtml.push('<br style="clear:both;">');
			}
		}
		if (ml_numberOfColumns === 3) {
			// nach jedem 3. Element ein Trenn-Element einfügen
			if (i%3 === 2) {
				legendHtml.push('<br style="clear:both;">');
			}
		}
		if (ml_numberOfColumns === 4) {
			// nach jedem 4. Element ein Trenn-Element einfügen
			if (i%4 === 3) {
				legendHtml.push('<br style="clear:both;">');
			}
		}
	}
	legendHtml.push('</ul>');
	return legendHtml.join("");
};

// Show/hide chart by click legend
var updateDataset = function(e, datasetIndex, clickedElement, chartID) {
	//console.log( 'CLICK', 'e:', e, 'datasetIndex:', datasetIndex, 'clickedElement:', clickedElement, 'chartID:', chartID );
	var index = datasetIndex;
//	var ci = e.view.myChart; // hard coded
	var ci = Chart.instances[chartID];
	//console.log( 'ci', ci );
	var meta = ci.getDatasetMeta(index);

	// See controller.isDatasetVisible comment
	meta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null;
	ci.data.datasets[index].hidden = !ci.data.datasets[index].hidden;
	
	applySuggestedMinMaxValues( ci );
	
	// We hid a dataset ... rerender the chart
	ci.update();
	
	// Legend Element aktualisieren
	legendUpdateElement(ci, datasetIndex, clickedElement, ci.data.datasets[index].hidden);
};

/**
 * HTML-Legende Active-Status aktualisieren anhand der Chart-Eigenschaften.
 * 
 * @param {type} chart
 * @param {type} datasetIndex
 * @param {type} element
 * @param {type} hidden
 * @returns {undefined}
 */
function legendUpdateElement(chart, datasetIndex, element, hidden) {
	//var legendText = chart.legend.legendItems[datasetIndex].text;
	//console.log( 'element:', element, 'hidden:', hidden );
	if (hidden) {
		//element.innerHTML = legendText;
		$(element).removeClass( 'ml-legend-item-active' );
	} else {
		//element.innerHTML = '<b>'+legendText+'</b>';
		$(element).addClass( 'ml-legend-item-active' );
	}
	
	fixTicksAtBarCharts( chart );
	forceCanvasRedrawOnOldDevices();
}

function legendUpdateVisibleStates( htmlLegendID, chartInstance ) {
	var $elements = $('#'+htmlLegendID + ' li');
	//console.log( 'elements:', $elements );
	var ml_numberOfColumns = chartInstance.options.legend.ml_numberOfColumns;
	//console.log('chart.options.legend.ml_numberOfColumns:', chartInstance.options.legend.ml_numberOfColumns);
	
	chartInstance.legend.legendItems.forEach( function(legendItem, index, array){
		//console.log( 'legendItem.hidden', legendItem.hidden );
		legendUpdateElement(chartInstance, index, $elements[index], legendItem.hidden);
	});
	
	// Die Einstellungen für RWD haben Vorrang!
	var _numberOfColumns_rdw = getHTMLLegend_numberOfColumns_rwd( chartInstance );
	if (!isNaN(_numberOfColumns_rdw)) {
		ml_numberOfColumns = _numberOfColumns_rdw;
	}
	
	// Mehrspaltigkeit
	if (ml_numberOfColumns === 1) {
		$elements.addClass( 'columns-1' );
	}
	if (ml_numberOfColumns === 2) {
		$elements.addClass( 'columns-2' );
	}
	if (ml_numberOfColumns === 3) {
		$elements.addClass( 'columns-3' );
	}
	if (ml_numberOfColumns === 4) {
		$elements.addClass( 'columns-4' );
	}
}

/**
 * X-Achsen Einteilungspositionierung einstellen/korrigieren (offsetGridLines).
 * 
 * - bei "bar" (Balken) muss die Skalen-Einteilungsbeschriftung mittig sein
 * - bei "line" (Linien) muss die Skalen-Einteilungsbschriftung punktgenau sein
 * 
 * @param {Chart} chart	Chart-Instanz.
 * @returns {undefined}
 */
function fixTicksAtBarCharts( chart ) {
	var _datasets = chart.config.data.datasets;
	
	function filterByVisibleNotBar( item ) {
		//console.log( 'item:', item );
//		if (item._meta[chart.id].hidden !== true && item._meta[chart.id].type !== 'bar') {
//			return item;
//		}
		if (item.hidden !== true && item._meta[chart.id].type !== 'bar') {
			return item;
		}
	}
	var datasets_filterByVisibleNotBar = _datasets.filter( filterByVisibleNotBar );
	//console.log( 'datasets_filterByVisibleNotBar:', datasets_filterByVisibleNotBar );//, e );

	var oldValue_offsetGridLines = chart.options.scales.xAxes[0].gridLines.offsetGridLines;

	if (datasets_filterByVisibleNotBar.length === 0) {
		// wenn alle sichtbaren datasets vom Typ "bar" sind
		chart.options.scales.xAxes[0].gridLines.offsetGridLines = true;
	} else {
		chart.options.scales.xAxes[0].gridLines.offsetGridLines = false;
	}

	if (oldValue_offsetGridLines !== chart.options.scales.xAxes[0].gridLines.offsetGridLines) {
		chart.update();
	}
}

/**
 * Y-Achse, 
 * 
 * @param {Chart} chart
 * @returns {undefined}
 */
function applySuggestedMinMaxValues( chart ) {
	var ci = chart;
	//console.log('chart', ci);
	var allChartsHidden = true;
	
	//console.log('allChartsHidden', allChartsHidden);
	if (ci.options.scales.yAxes[0].ticks['ml_yAxis_dynamicScaling'] === true) {
		// suggestedMin/suggestedMax nur zuweisen, wenn alle Charts eines Diagramms ausgeblendet sind,
		// damit auf der Y-Achse keine utopischen Werte auf der Skala stehen!
		ci.data.datasets.forEach(function(item, index, array){
			//console.log(index, 'hidden:', item.hidden);
			if (item.hidden === false) {
				allChartsHidden = false;
			}
		});
		
		if (allChartsHidden) {
			ci.options.scales.yAxes[0].ticks.suggestedMax = ci.options.scales.yAxes[0].ticks.ml_suggestedMax;
			ci.options.scales.yAxes[0].ticks.suggestedMin = null; // ci.options.scales.yAxes[0].ticks.ml_suggestedMin; // 0 gesetzt, @since 1.4.0
		} else {
			ci.options.scales.yAxes[0].ticks.suggestedMax = null;
			ci.options.scales.yAxes[0].ticks.suggestedMin = null;
		}
	} else {
		if (ci.options.scales.yAxes[0].ticks.hasOwnProperty('suggestedMax') === false) {
			ci.options.scales.yAxes[0].ticks.suggestedMax = ci.options.scales.yAxes[0].ticks.ml_suggestedMax;
			ci.options.scales.yAxes[0].ticks.suggestedMin = ci.options.scales.yAxes[0].ticks.ml_suggestedMin;
			ci.update(); // notwendig für das allererste Mal nach der Instanzierung
		}
	}
}

/**
 * Gibt die Spaltenanzahl bezogen auf die verschiedenen viewport-Werte für Bootstrap
 * ('xs', 'sm', 'md', 'lg') zurück.
 * 
 * @param {Chart} chart
 * @returns {getHTMLLegend_numberOfColumns_rwd.ml_htmlLegend_numberOfColumns_rwd|undefined}
 */
function getHTMLLegend_numberOfColumns_rwd( chart ) {
	var ml_htmlLegend_numberOfColumns_rwd = chart.options.legend.ml_htmlLegend_numberOfColumns_rwd;
	//var viewportSize = viewport.getViewPortSize(); // umfasst Bildschirmbreite
	var viewportSize = viewport.getChartContainerSize(); // umfasst die Chart-Breite, da die Charts auch nebeneinander dargestellt werden
	//console.log( 'viewportSize:', viewportSize );
	
	if (ml_htmlLegend_numberOfColumns_rwd !== null) {
		var _numberOfColumns_rdw = ml_htmlLegend_numberOfColumns_rwd[ viewportSize ];
		return _numberOfColumns_rdw;
	} else {
		return;
	}
}

//------------------------------------------------------------------------------
// 
// Scroll-up Button
//
//------------------------------------------------------------------------------
// Quelle: https://www.w3schools.com/howto/howto_js_scroll_to_top.asp
/*
// When the user scrolls down 20px from the top of the document, show the button
window.onscroll = function() {scrollFunction()};

function scrollFunction() {
    if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
        document.getElementById("scrollup_btn").style.display = "block";
    } else {
        document.getElementById("scrollup_btn").style.display = "none";
    }
}

// When the user clicks on the button, scroll to the top of the document
function topFunction() {
    document.body.scrollTop = 0; // For Chrome, Safari and Opera 
    document.documentElement.scrollTop = 0; // For IE and Firefox
}
*/

//------------------------------------------------------------------------------
// Event-Handler
//------------------------------------------------------------------------------
function updateWatchmodeBtn_position() {
	var mode = "single";
	var total_width = $('body').width();
	
	if ($('#chart-container-parent_2').css('display') !== 'none' ) {
		mode = "compare";
	}
	
	if (mode === 'single') {
		var content_width = $('#chart-container-parent_1').width() + $('#chart-container-parent_1').offset().left + 10; // 10px padding
		var space_right = total_width - content_width;
		
		//console.log('space_right', space_right);
	} else if (mode === 'compare') {
		var content_width = $('#chart-container-parent_2').width() + $('#chart-container-parent_2').offset().left + 10; // 10px padding
		var space_right = total_width - content_width;
	}
	
	if (space_right > 70) {
		$('#watchmode_btn').css('left', content_width+10+'px');
	} else {
		$('#watchmode_btn').css('left', 'initial');
	}
	
}

function updateHTMLLegends() {
	for (var prop in Chart.instances) {
		var chartInstance = Chart.instances[prop];
		var htmlLegendMarkup = chartInstance.generateLegend();
		var htmlLegend = document.getElementById( chartInstance.ml_htmlLegendID );
		if (htmlLegend !== null) {
			htmlLegend.innerHTML = htmlLegendMarkup;
			try {
				var _legend_title = chartInstance.tooltip._options.ml_configDiagramObj.legend_title;
				if (_legend_title) {
					$('#'+chartInstance.ml_htmlLegendID).prepend('<div class="ml-legend-title">'+_legend_title+'</div>');
				}
			} catch(e){}
		}
		legendUpdateVisibleStates( chartInstance.ml_htmlLegendID, chartInstance );
	}
}
function updateChartByRWD() {
	var viewportSize = viewport.getChartContainerSize(); // umfasst die Chart-Breite, da die Charts auch nebeneinander dargestellt werden
	
	try {
		// klappt nur, wenn ChartInstanzen da sind
		for (var prop in Chart.instances) {
			var chartInstance = Chart.instances[prop];
			// wir brauchen nicht testen, ob es sich um eine chart vom Typ "line" handelt,
			// wird ignoriert bei anderen Typen
			chartInstance.options.elements.point.radius = config.ml_pointRadius_rwd[ viewportSize ];
			chartInstance.options.elements.point.hoverRadius = config.ml_pointRadius_rwd[ viewportSize ];
			chartInstance.update();
			forceCanvasRedrawOnOldDevices();
		}
	} catch(e) {}
}


//------------------------------------------------------------------------------

/**
 * BugFix: doppelter canvas content bei Standard-Browsern auf alten Android Geräten (vor allem Samsung)
 * 
 * Quellen: https://stackoverflow.com/questions/18271990/android-native-browser-duplicating-html5-canvas-fine-in-chrome
 *          https://issuetracker.google.com/issues/36952029
 * @returns {undefined}
 */
function forceCanvasRedrawOnOldDevices() {
	$('canvas').each(function(){
		$(this).css('opacity', '0.99');
		//$(this).css('border', '1px solid red'); // debugging
		setTimeout(function () {
			$('canvas').each(function(){
				$(this).css('opacity', 1);
				//$(this).css('border', '1px solid green'); / debugging
			});
		}, 1000);
	});
}


// https://gist.github.com/steveh80/288a9a8bd4c3de16d799
var viewport = (function() {
	var viewPorts = ['xs', 'sm', 'md', 'lg'];

	var viewPortSize = function() {
		return window.getComputedStyle(document.body, ':before').content.replace(/"/g, '');
	};

	var is = function(size) {
		if ( viewPorts.indexOf(size) === -1 ) throw "no valid viewport name given";
		return viewPortSize() === size;
	};

	var isEqualOrGreaterThan = function(size) {
		if ( viewPorts.indexOf(size) === -1 ) throw "no valid viewport name given";
		return viewPorts.indexOf(viewPortSize()) >= viewPorts.indexOf(size);
	};
	
	// Spezial-Lösung für Buchner-Charts
	var chartContainerSize = function() {
		var w = $('#chart-container_1').width();
		//console.log( 'w:', w );
		if (w < 341) {
			return 'xs';
		} else if (w < 443) {
			return 'sm';
		} else if (w < 543) {
			return 'md';
		} else {
			return 'lg';
		}
	};

	// Public API
	return {
		is: is,
		isEqualOrGreaterThan: isEqualOrGreaterThan,
		getViewPortSize: viewPortSize,
		getChartContainerSize: chartContainerSize
	};
})();

