/**
 * http://tasuke.dna.affrc.go.jp/
 *
 * Author: Norio Tabei
 *
 * Copyright (c) 2025 National Institute of Agrobiological Sciences
 *
 */
var DEBUG_MODE = false;
//var DEBUG_MODE = true;

//var storage = sessionStorage;	// Refer to it in env.js
var keyState = {};

// For Accs/genome-region selection
var selectedAccReg = {};
// undo/redo buffer
var maxUndoBuff = 20;
var undoBuff = [];	// Each element is clone of selectedAccReg["accs"]. Always elements>=1 since it includes the current history.
var redoBuff = [];
var accListManager = null;

// For image buffers
// with selection
var bufViewImage = null;
var bufTreeImage = null;
var bufTitleImage = null;
var bufScaleImage = null;
//without selection
var bufViewImageOri = null;
var bufTreeImageOri = null;
var bufTitleImageOri = null;
var bufScaleImageOri = null;

// For systemTree
var mainTreeobj = null;
var systreeFlg = false;

//For VariantDns
var scaleGenomes = null;
var scaleAdmixure = null;

//For PCA-graph
var graphPcaA = null;
var graphPcaB = null;
var pcaFlg = false;

// Data container
var variantDensInfoTempJson = null;	// A template for creating variantDensInfo from variantDensInfoOri ("varcount","snpeff","pca" is empty)
var variantDensInfoOri = null;	// Original data. 
var variantDensInfo = null;	// Re-count data from "variantDensInfoOri".
var admixtureInfoOri = null;
var pcaInfoOri = null;

//Style
var mgStyle = {
	selectingRow: "rgba(0,255,0,0.3)",
	selectedRow: "rgba(255,0,0,0.25)",
	selectedInfoBg: "rgba(80,120,255,0.3)",
	regionMask: "rgba(0,0,0,0.6)",
	regionMaskFrame: "rgba(255,255,255,0.9)"
};

// Dafault value
var DEF_VIEWCVS_WIDTH = 700;
var DEF_VIEWCVS_WIDTH_M = 1280;
var DEF_BLOCKH = 8;
var DEF_BLOCKW = 5;
var DEF_BLOCKSIZE = 10000;
var DEF_KMERW = 100;
var DEF_PCA_GRAPH_WIDTH = 420;
var DEF_PCA_GRAPH_HEIGHT = 460;
var DEF_POPUP_TIMELAG = 1400;
var DEF_ADMIXTURE_COLORS = ["#b1997d", "#a2dbff", "#107a78", "#8ad627", "#fb0081", "#c4b3c4", "#ece27a", "#c06d50", "#9710ff", "#40a0d2",
	 "#5f6bf1", "#97486f", "#4ac0a9", "#fa55d5", "#caae0d", "#e48b63", "#73a7ff", "#e05ffc"];

var BLOCKW = DEF_BLOCKW;
var BLOCKSIZE = DEF_BLOCKSIZE;
var KMERW = DEF_KMERW;
var HEATMAP_SCALE = 260;
var FLOAT_BYTE = 4;
//var DEF_QUAL = 1;
//var DEF_DP = 1;
//var DEF_DP4 = 1;
var MOUSE_POPUP_OFFSET_X = -1;
var MOUSE_POPUP_OFFSET_Y = -10;
var ADMIXTURE_COLORS = DEF_ADMIXTURE_COLORS;
//var ADMIXTURE_COLORS = ["#c1a98d", "#a2dbff", "#107a78", "#8ac627", "#fb0081", "#b4c3b4", "#ece27a", "#c06d50", "#8700ff", "#4080c2", "#53a7ff", "#77284f"];
//var ADMIXTURE_COLORS = ["crimson", "gray","royalblue","seagreen", "olive", "saddlebrown", "violet", "skyblue", "navy", "teal", "gold", "fuchsia"];

/**
 * Asynchronous Ajax
 */
var getNewickTotalBytes = 0;
function mg_render() {
	mg_showLoadingDialog();	// The dialog opened here is closed by getAndDrawVaraintDencity().

	// If you want to run multiple ajax in sequence, you need to nest.
	// This is implemented by the recursive processing.
//	var sendData = {};
	var procs = [];

	// Bellow, sync functions.
//	if(_isEmpty(storage.getItem("_Env"))){
	if(true) {	// To prevent users from overwriting "enable*" parameters, be sure to read them every time.
		procs.push({
			"url": "./bin/env.php",
			"type": "GET",
			"async": true,
			"preFunc": null,
			"func": function(data) {
				storage.setItem("_Env", data);
			},
			"errorFunc": function(jqXHR, textStatus, errorThrown) { alert("Failed to get settings from server."); }
		});
	}
	if(_isEmpty(storage.getItem("_Lists-origin"))){
		procs.push({
			"url": "./bin/list.php",
			"type": "POST",
			"async": true,
			"preFunc": function() {
				// for "URL?acc=acc1,acc2,acc3...."
				return {"prm_ext_acc": ""};
			},
			"func": function(data) {
				var a=data.split("|");
				var accArray = JSON.parse(a[0]);
				mg_setAccListObjArray("_Lists-origin", accArray);
				if(!_isEmpty(storage.getItem("mg_ext_accessions"))) {
					var listsAccs = storage.getItem("mg_ext_accessions").split(",");
					storage.removeItem("mg_ext_accessions");
					mg_reset_accession_lists_byArray(listsAccs);
				} else {
					mg_setAccListObjArray("_Lists", accArray);
				}
			},
			"errorFunc": function(jqXHR, textStatus, errorThrown) { alert("Failed to get accession list from server."); }
		});
	} else {
		var accArray = mg_getAccListObjArray("_Lists");
		mg_makeAcc2IndexMap(accArray);	// Dummy action. set acc2IndexMap. 
	}
	if(_isEmpty(storage.getItem("_Chrs"))){
		procs.push({
			"url": "./bin/chr.php",
			"type": "GET",
			"async": true,
			"preFunc": null,
			"func": function(data) {
				storage.setItem("_Chrs", data);
			},
			"errorFunc": function(jqXHR, textStatus, errorThrown) { alert("Failed to get chromosome list from server."); }
		});
	}
	if(_isEmpty(storage.getItem("_AllPhenoExp"))) {
		procs.push({
			"url": "./bin/getAllPhenoexp.php",
			"type": "GET",
			"async": true,
			"preFunc": null,
			"func": function(data) {
				storage.setItem("_AllPhenoExp", data);
			},
			"errorFunc": function() { alert("Failed to get Phenotype-Exp table from server."); }
		});
	}
	if(true) {
		// blockSize(for Macro-genome VariantCount) is needed for transfer-size expection.
		// This proccess is very light.
		procs.push({
			"url": "./bin/getMacrogenome.php",
			"type": "POST",
			"async": true,
			"preFunc": function() {
				return {"prm_Mode": "blockSizeOnly"};
			},
			"func": function(data) {
				try {
					var res = JSON.parse(data);
					variantDensInfoOri = res;	// Global var.
					if(res["varcount"] && "stat" in res["varcount"] && "blockSize" in res["varcount"]["stat"]) {
						var blockSize = parseInt(res["varcount"]['stat']['blockSize']);
						if(DEBUG_MODE) console.log("VariantCount-blockSize:" + blockSize); 
						BLOCKSIZE = blockSize
//						storage.setItem("mg_zoom", blockSize);
					}

					if(res["admixture"] && "stat" in res["admixture"]) {
						if("kmers" in res["admixture"]["stat"]) {
							var kmersStr =  JSON.stringify(res["admixture"]["stat"]["kmers"]);
							if(DEBUG_MODE) console.log("Admixture-Kmers:" + kmersStr);
							storage.setItem("mg_kmers", kmersStr);
						} else {
							storage.setItem("mg_kmers", '[]');
						}
						if("floatByte" in res["admixture"]["stat"]) FLOAT_BYTE = parseFloat(res["admixture"]["stat"]["floatByte"]);
					}

					if(res["pca"] && "stat" in res["pca"]) {
						if("pcs" in res["pca"]["stat"]) {
							 if(DEBUG_MODE) console.log("PCA-PCs:" +  res["pca"]["stat"]["pcs"]);
							 storage.setItem("mg_pcs", parseInt(res["pca"]["stat"]["pcs"]));
						}
						if("floatByte" in res["pca"]["stat"]) FLOAT_BYTE = parseFloat(res["pca"]["stat"]["floatByte"]);

						pcaFlg = true;
					}
				} catch(e) {
					console.log(e);
				}
			},
			"errorFunc": function(jqXHR, textStatus, errorThrown) { alert("Failed to get Macro-genome VariantCount blockSize."); }
		 });
	}

	var phylipTreeNewick = storage.getItem("_PhylipTreeNewick");
	if(_isEmpty(storage.getItem("_SysTreeNewick")) && !phylipTreeNewick) {
		procs.push({
			"url": "./bin/getNewick.php",
			type: "POST",
			async:true,
			xhr: function () {
				var xhr = $.ajaxSettings.xhr();
				xhr.onprogress = function(evt) {
					if(evt) {
						console.log("getNewick.php loaded:" + evt.loaded + " / " + getNewickTotalBytes);
						mg_updateProgress(evt.loaded / getNewickTotalBytes * 100);
					}
				};
				xhr.onloadend = function(evt) {
					mg_updateProgress(100);
				};
				return xhr;
			},
			"preFunc": function() {
				var en = JSON.parse(storage.getItem("_Env"));
				var ret = null;
				var array = [];
				if('systreeNewickLW' in en && en['systreeNewickLW']) {
					// From newick.
					localTreeObj = new PhyloTreeCreater(null);
					localTreeObj.parseNewick(en['systreeNewickLW']);

					array = mg_getAccListObjArray("_Lists");
					if(array.length >= 3) {
						removeNotExistAccFromTreeObj(localTreeObj);					

						// Replace accId -> dbId.
						var accId2ObjMap = mg_getAccId2ObjMap();
						var dbNames = [];
						for(var i=0,len=localTreeObj.order.length; i<len; i++) {
							dbNames.push(accId2ObjMap[localTreeObj.order[i].childs].db);
						}
						localTreeObj.changeLeafName(dbNames);

						var nwk = localTreeObj.tree.getNewick();
						storage.setItem("_SysTreeNewick", nwk);
						storage.setItem('_SysTreeNewickLW', nwk);
					}
					if(localTreeObj) localTreeObj.initTree();	// Release memory.

					// Remove newick from "_Env" storage.
					delete en['systreeNewickLW'];
					var data = JSON.stringify(en);
					storage.setItem("_Env", data);
					this.url = "";	// Skip this.func().
					mg_updateProgress(100);
//					return null;
				} else {
					// From distance matrix.
					var lists = mg_getAccListObjArray("_Lists");
//					var array = [];
					for(var i=0, len=lists.length; i<len; i++) array.push(lists[i].db);
					array.sort();	// Perhaps tree changes in order of Acc, so string sort for reproducibility.

					getNewickTotalBytes = lists.length * en.systreeProgressCharLen;	// Global va	// Global var

					ret = {"prm_Lists": array.join(',')};
					if(array.length < 3 ) {
						this.url = "";	// Cancel this process.
					} else {
						_changeProgressStr("Creating system tree...");
					}
/*
					if(array.length < 3 ) {
						this.url = "";	// Cancel this process.
						en.useSystemTree = "0";
						en.useSystemTreeLW = "0";
						storage.setItem("_Env", JSON.stringify(en));
					} else {
						_changeProgressStr("Creating system tree...");

					}
					return {"prm_Lists": array.join(',')};
*/
				}
				if(array.length < 3 ) {
					en.useSystemTree = "0";
					en.useSystemTreeLW = "0";
					storage.setItem("_Env", JSON.stringify(en));
				}

				return ret;
			},
			"func": function(data) {
				_defProgressStr();
				var en = JSON.parse(storage.getItem("_Env"));

				if(/Error:/.test(data)) alert(data + " Check out the Phylip programs and distance matrix.");
				mg_clearSystemTreeCache();
				repRegexp = new RegExp("^[\\" + en.systreeProgressChar + "\\s]+");
				data = data.replace(repRegexp, '');
				var retObj = JSON.parse(data);
				if( !('newick' in retObj) ) {
					if(DEBUG_MODE) console.log("SystemTree is not registered.");
					return;
				} 
				var nwk = retObj['newick'];
				var needRooting = retObj['needrooting'];

				// Midpoint rooting.
				if(needRooting) {
					var midtree;
					try{
						var dataCnv = rootingMidpoint(nwk);
						midtree = parseNewick(dataCnv);
						nwk = midtree.tree.getNewick();
					} catch(e) {
						// If rooting fails, use original newick.
						console.error(e);
					} finally {
						mg_clearParseNewick(midtree);
					}
				}
				storage.setItem("_SysTreeNewick", nwk);
				if(!storage.getItem("_SysTreeNewickLW")) storage.setItem("_SysTreeNewickLW", nwk);
			},
			"errorFunc": function(jqXHR, textStatus, errorThrown) {
				console.log(errorThrown);
			}
		});
	} else if(phylipTreeNewick) {
		storage.setItem("_SysTreeNewick", phylipTreeNewick);
//		storage.removeItem("_PhylipTreeNewick");
	}

	var recProc = function() {
		var proc = procs.shift();
		if(!proc) {
			mg_renderInner();
			return;
		}
		var sendData = {};
		if(proc.preFunc != null) sendData = proc.preFunc();
		if(!proc.url) {
			recProc();
			return;
		}

		var ajaxData = {
			"url": proc.url,
			"cache": false,
			"type": proc.type,
			"async": proc.async,
			"data": sendData
		};
		if("xhr" in proc) ajaxData["xhr"] = proc.xhr;

		if(DEBUG_MODE) console.log(proc.url + ": Start ajax (async:" + proc.async + ")");
		$.ajax(ajaxData).done(function(data){
			if(DEBUG_MODE) console.log(proc.url + ": done ajax (async:" + proc.async + ")");
			proc.func(data);
//			sendData = {};
			if(DEBUG_MODE) console.log(proc.url + ": Cal next recPro (async:" + proc.async + ")");
			recProc();	// Recursive processing

		}).fail(function(jqXHR, textStatus, errorThrown){
			proc.errorFunc(jqXHR, textStatus, errorThrown);
		});
	};
	recProc();
}
function mg_renderInner() {
	mg_initCache();
	mg_initForm();
	mg_initEvents();

	var en = new Object(JSON.parse(storage.getItem("_Env")));
	if(storage.getItem("_SysTreeNewick")) {
//	if(en.useSystemTree === "1" || en.useSystemTreeLW === "1") {
		systreeFlg = true;
	} else {
		systreeFlg = false;
	}
	var cvsTreeId = "cvs_tree";
	var $cvsTree = $("#" + cvsTreeId);
	if(systreeFlg) {
		$cvsTree.css("display", "block");
		$(".systemtree").width(110);
		mainTreeobj = mg_createSystemTreeObj(cvsTreeId);
		syncList2Tree2List();
	} else {
		$cvsTree.css("display", "none");
		$(".systemtree").width(0);
		// "_Lists" is always registered, so do nothing here.
	}
	addHistory();	// Add empty undoBuff.

	initSelect2();
	mg_drawTitle();
	getAndDrawVaraintDencity();
	setVersionToAbout();
}

function mg_initCache() {
	var en = new Object(JSON.parse(storage.getItem("_Env")));

	// Below, inherit TASUKE's storage.

	if(_isEmpty(storage.getItem('_Color'))) {
		storage.setItem('_Color', 0);
	}
	if(_isEmpty(storage.getItem("_TitleW"))){
		storage.setItem("_TitleW", 130);
	}
	if(_isEmpty(storage.getItem("_Name"))) {
		storage.setItem("_Name", "id");
	}
	if(storage.getItem("_NameSub") === null) {
//	if(_isEmpty(storage.getItem("_NameSub"))){
		storage.setItem("_NameSub", "variety");
	}
	if(_isEmpty(storage.getItem("_Chr"))){
		storage.setItem("_Chr", "");
	}
	storage.setItem("_popupDetail", 1);
	if(storage.getItem("_SystemTreeSort") == null) {
		if("systreeSort" in en && (en.systreeSort === "asc" || en.systreeSort === "desc")) {
			storage.setItem("_SystemTreeSort", en.systreeSort);
		} else {
			storage.setItem("_SystemTreeSort", "desc");
		}
	}
	if(_isEmpty(storage.getItem("_SystemTreeMode"))) {
		storage.setItem("_SystemTreeMode", "phylogram");
	}
	if(storage.getItem("_SystemTreeCorrect") == null) {
		if("systreeCorrection" in en && /^asec/.test(en.systreeCorrection)) {
			storage.setItem("_SystemTreeCorrect", en.systreeCorrection);
		} else {
			storage.setItem("_SystemTreeCorrect", "");	// None
		}
	}
	if(_isEmpty(storage.getItem("_SystemTreeSortType"))) {
		if(en.systreeSortType) {
			storage.setItem("_SystemTreeSortType", en.systreeSortType);
		} else {
			storage.setItem("_SystemTreeSortType", 'subtree');
		}
	}
	if(_isEmpty(storage.getItem("_SystemTreeW"))) {
		if("systreeWidth" in en && en.systreeWidth != "" && parseInt(en.systreeWidth) > 0) {
			storage.setItem("_SystemTreeW", parseInt(en.systreeWidth));
		} else {
			storage.setItem("_SystemTreeW", 110);
		}
	}
	if( ('colorsAdmixture' in en) && en["colorsAdmixture"] &&  en["colorsAdmixture"] instanceof Array && en["colorsAdmixture"].length > 0) {
		ADMIXTURE_COLORS = en["colorsAdmixture"];
	}
}

var timeoutListSearch = null;
function mg_initForm() {
	var en = JSON.parse(storage.getItem("_Env"));

	var $ulMenuExport = $("#ul_menu_export");
	if(variantDensInfoOri["varcount"]) {
		$("#sel_viewmode").append(new Option("Variant count(Impact=high)", "snpeff", false, false));

		if(en["enableExportVariantDensFile"]) {
			$ulMenuExport.append("<li><a href=\"javascript:exportVariantCountFile('dens_arr')\">Current Variant count</li>");
			$ulMenuExport.append("<li><a href=\"javascript:exportVariantCountFile('eff_arr')\">Current Variant count (impact=high)</li>");
		}
	}
	if(variantDensInfoOri["admixture"]) {
//		$("#sel_viewmode").append(new Option("ADMIXTURE", "admixture", false, false));
		$("#sel_viewmode").append(new Option("ADMIXTURE", "admixture", false, true));

		if(en["enableExportAdmixtureFiles"]) $ulMenuExport.append("<li><a href=\"javascript:exportAdmixtureFiles()\">Current ADMIXTURE dataset (zip)</li>");
	}
	if(pcaFlg) {
		$("#sel_viewmode").append(new Option("PCA", "pca", false, false));

		if(en["enableExportPcaFiles"]) $ulMenuExport.append("<li><a href=\"javascript:exportPcaFiles()\">Current PCA dataset (txt or zip)</li>");
	}

	$("#sel_chr").multipleSelect({
		width:110,
		formatSelectAll: function() {
			return '(All)';
		},
		formatAllSelected: function() {
			return '(All selected)';
		}
	});
	syncMode2Form();

	var $sel_blockh = $("#sel_blockh");
	var hs = [1,2,3,4,5,6,7,8,9,10,11,12];
	for(var i=0,len=hs.length; i<len; i++) {
		var opt = new Option(hs[i], hs[i], false, false);
		$sel_blockh.append(opt);
	}
	$sel_blockh.val(DEF_BLOCKH);
	adjustBlockHForFirefox();
}
function initSelect2() {
	// Search accession form(select2).
	var $select2 = $("#sel_search");
	$select2.select2({
		dropdownAutoWidth: true,
		width: 'auto',
		dropdownParent:$("#sel_search_parent"),
		closeOnSelect: false,
		placeholder: "Search by keyword",
		matcher: function(params, data) {
			item = data.element.getAttribute("searchval");
			return mg_customMatcher(params, data, item);
		}
	 });
	                
	updateSearchSelect();
	$select2.on('change', function(e) {
		var $sel = $(this);
		var selAcc = $sel.val();

		// Change track.
		if("accs" in selectedAccReg && selAcc in  selectedAccReg["accs"]) {
			delete  selectedAccReg["accs"][selAcc];
		} else {
			if( !("accs" in selectedAccReg) ) selectedAccReg["accs"] = {};
			if(selAcc) selectedAccReg["accs"][selAcc] = true;	// Placeholder value is ""(skip).
		}
		reflectSelectionWhole();
		$sel.prop('selectedIndex', 0).trigger("change.select2");	// Change to placeholder

		// Scroll to targetRow.
		mg_scrollTo(selAcc);

		// Highlight selected options.
		markSelect2Options(this);

		if($("#div_select_idlist").is(":visible")) syncMg2AccListTabulator();
	});
	$select2.on('select2:open', function (e) {
		// Set dropdown height.
		// Since the dropdown does not exist in the initial state, it must be prepared in the "select2:open" event.
		$('#' + this.id + '_parent .select2-results__options').css('max-height', '150px');

//		$('#' + this.id + '_parent .select2-dropdown').css('background', '#f6f2f0');
		var $objs = $('#' + this.id + '_parent').find('.select2-container--open');
		var defaultStyle = $($objs[1]).attr('style');
		$($objs[1]).css('cssText', defaultStyle + 'right:0px; left:auto; width:500px;');

		// Below, there is a timelag in updating ".select2-results__options", so setTimeout is required.
		setTimeout(function() {
			// Apply highlights after opening the dropdown.
			markSelect2Options(this);
		}.bind(this), 10);

		$('#' + this.id + '_parent .select2-search__field').on('input', function(e) {
			// Update highlights after filtering by keyword.
			clearTimeout(timeoutListSearch);
			timeoutListSearch = setTimeout(function() {
				markSelect2Options(this);
				timeoutListSearch = null;
			}.bind(this), 100);
		}.bind(this));
		$('#sel_search_parent .select2-selection__rendered').css('font-size', '11px');
		if(!$("#sel_search_btn_selall").length) {
			$("#sel_search_parent .select2-search__field").css("width", "91.5%");
			var btnHtml = "<span style=\"position:absolute;top:7px;right:2px;padding:1px;font-size:14px;\">" + 
				"<i id=\"sel_search_btn_clearfilter\" class=\"fa fa-times\" aria-hidden=\"true\" style=\"cursor:pointer;margin-right:7px;\" title=\"Clear filter\"></i>" + 
				"<i id=\"sel_search_btn_selall\" class=\"fa fa-plus\" aria-hidden=\"true\" style=\"cursor:pointer;margin-right:3px;\" title=\"Select filtered\"></i>" + 
				"<i id=\"sel_search_btn_deselall\" class=\"fa fa-minus\" aria-hidden=\"true\" style=\"cursor:pointer;\" title=\"Deselect filtered\"></i>" +
				"</span>";
			$("#sel_search_parent .select2-search--dropdown").append(btnHtml);
			$("#sel_search_btn_selall").on("click", function() {
				mg_selectQuickSearch(true);
/*
				var lis = $("#sel_search_parent .select2-results__options li");
				var lenLis = lis.length;
				if(lenLis > 0 && !("accs" in selectedAccReg)) selectedAccReg["accs"] = {}; 
				var topId = null;
				for(var i=0; i<lenLis; i++) {
					var idstr =  $(lis[i]).attr('id');
					if(!idstr) continue;
					var arr  = idstr.split("-");
					var id = arr.pop();
					selectedAccReg["accs"][id] = true;
					if(topId === null) topId = id;
				}
				$("#sel_search").trigger("change");
				mg_scrollTo(topId);
*/
			});
			$("#sel_search_btn_deselall").on("click", function() {
				mg_selectQuickSearch(false);
			});
			$("#sel_search_btn_clearfilter").on("click", function() {
//				$("#sel_search").val(null).trigger("change");	// Dont work.
				var $filterForm = $("#sel_search_parent .select2-search__field");
				if($filterForm.val() === "") return;
				$filterForm.val("").trigger("input");
			});
		}
	});
}
function mg_selectQuickSearch(select=true) {
	var lis = $("#sel_search_parent .select2-results__options li")
	var lenLis = lis.length;
	if(!("accs" in selectedAccReg)) selectedAccReg["accs"] = {};
	var topId = null;
	for(var i=0; i<lenLis; i++) {
		var idstr =  $(lis[i]).attr('id');
		if(!idstr) continue;
		var arr  = idstr.split("-");
		var id = arr.pop();
		if(select) {
			selectedAccReg["accs"][id] = true;
		} else {
			delete selectedAccReg["accs"][id];
		}
		if(topId === null) topId = id;
	}
	if(!topId) return;
	$("#sel_search").trigger("change");
	if(select) mg_scrollTo(topId);
}
function mg_scrollTo(selAcc) {
	if(!selAcc || $("#sel_viewmode").val() === "pca") return;

	var id2Index = mg_getAcc2IndexMap();
	var targetSelectedIndex = null;
	if(selAcc in id2Index) targetSelectedIndex = id2Index[selAcc];
/*
	var targetSelectedIndex = null;
	var accInfoArr = mg_getAccListObjArray("_Lists");
	for(var i=0,len=accInfoArr.length; i<len; i++) {
		if(accInfoArr[i]["id"] === selAcc) {
			targetSelectedIndex = i;
			break;
		}
	}
*/
	if(targetSelectedIndex === null) return;

	var blockH = parseInt($("#sel_blockh").val());
	var $div = $("#main_view_div");
	var targetY = targetSelectedIndex * blockH;
	if(targetY >= $div.scrollTop() && targetY+blockH <= $div.scrollTop() + $div.height() - 1) {
		// The position is currently being displayed so it will not scroll.
	} else {
		var sctop = targetY - $div.height() / 2;
		if(sctop < 0) sctop = 0;
		$div.animate({scrollTop:sctop}, 100);
	}
}

function mg_setCss(elem, name, val) {
	$(elem).css(name, val);
}
function removeNotExistAccFromTreeObj(treeObj) {
//        var lists = mg_getAccListObjArray('_Lists-origin');
	var lists = mg_getAccListObjArray('_Lists');
        var existIds = {};
        for(var i=0,len=lists.length; i<len; i++) existIds[lists[i].id] = true;

        var treeAccs = treeObj.getNameOrder();
        var delAccs = [];
        for(var i=0,len=treeAccs.length; i<len; i++) {
                if( !(treeAccs[i] in existIds) ) {
                        delAccs.push(treeAccs[i]);
                        if(DEBUG_MODE) console.log("Remove from LW tree:" + treeAccs[i]);
                }
        }

        if(delAccs.length !== 0) treeObj.removeLeafsAndUpdate(delAccs);
}
function initAccListTabulator() {
	var lists = mg_getAccListObjArray("_Lists").concat();	// Deep copy.
	var accColumns = TasukeTabulatorManager.getStandardColumns();
	if(storage.getItem('_AllPhenoExp')) {
		// Get Gwas expression and add it's column.
		var acc2list = [];
		for(var i=0,len=lists.length; i<len; i++) {
			lists[i] = Object.assign({}, lists[i]);	//Copy object(not reference)
			acc2list[lists[i].id] = lists[i];
		}
		var phenoExpObjs = JSON.parse(storage.getItem('_AllPhenoExp'));
		var phenoids = Object.keys(phenoExpObjs);
		for(var i=0,iLen=phenoids.length; i<iLen; i++) {
			var nameAndExps = phenoExpObjs[phenoids[i]];
			var gwasColumn = TasukeTabulatorManager.getPhenotypeColumn(phenoids[i], nameAndExps[0]);
			gwasColumn['visible'] = true;
			accColumns.push(gwasColumn);

			var accs = Object.keys(nameAndExps[1]);
			for(var j=0,jLen=accs.length; j<jLen; j++) {
				if(!acc2list[accs[j]]) continue;
				acc2list[accs[j]][phenoids[i]] = parseFloat(nameAndExps[1][accs[j]]);
			}
		}
	}
	if(pcaFlg) {
		accColumns.push(
			{title:"FID(PCA)", field:"fid", width:120, editor:false}
		);
		var dbOrder = pcaInfoOri["stat"]["dbOrder"];
		var db2fid = {};
		for(var i=0,len=dbOrder.length; i<len; i++) {
			db2fid[dbOrder[i]["db"]] = dbOrder[i]["fid"];
		}
		for(var i=0,len=lists.length; i<len; i++) {
			if(lists[i]["db"] in db2fid) {
				lists[i]["fid"] = db2fid[lists[i]["db"]];
			} else {
				lists[i]["fid"] = "";
			}
		}
	}
/* Pending code.
	if(admixtureInfoOri) {
		var kmers = admixtureInfoOri["stat"]["kmers"];
		for(var i=0,iLen=kmers.length; i<iLen; i++) {
			var kColDef = {title:"K=" + kmers[i], field:"k" + kmers[i], headerFilter:false, editor:false, rowSpan:1, columns:[]};
			var cols = admixtureInfoOri["stat"]["cols"][i];
			for(var j=1; j<=cols; j++) {
				kColDef["columns"].push({title:j, field:"k" + kmers[i] + "-" + j, headerFilter:false, editor:false});
			}
			accColumns.push(kColDef);
		}
	}
*/
	var callbacks = {
		tableBuilt: function(manager) {
			var div = $("#div_idlist_tabulator");
			div.css("height", "");	// Remove the height set by Select2. To enable top/bottom etc.
			accListManager.initActionSelectorSimple('mg_std_actCheck', 'mg_std_actUncheck', 'mg_std_actToggle');
		},
		dataFiltered: function(manager, filters, rows) {
			accListManager.setCurrentRows(rows);
		}
	};
	accListManager =  new TasukeTabulatorManager("div_idlist_tabulator", accColumns, lists, callbacks, {defSelValue:false, movableRows:false});
	accListManager.verbose = DEBUG_MODE;
	accListManager.spanIdCurrentRows = "mg_std_CurrentRows";
	accListManager.spanIdCheckedRows = "mg_std_CheckedRows";
	accListManager.oncheck = function(repId = null) {
		if(DEBUG_MODE) console.log("Call tasukeTabulatorManager.oncheck():" + repId);

		var selIds = {};
		var rows = this.table.getRows();
		for(var i=0,len=rows.length; i<len; i++) {
			var rowData = rows[i].getData();
			if(rowData["sel"]) selIds[rowData["id"]] = true;
		}
//		applyHistory(selIds);	// Dont use. syncMg2AccListTabulator() is executed(cyclic process).
		selectedAccReg["accs"] = selIds;
		flgFromAcclist = true;
		drawSelectionWhole();
		addHistory(); 

		if(repId) mg_scrollTo(repId);
	};
}
function syncMg2AccListTabulator() {
	if(DEBUG_MODE) console.log("Exec macrogenome.syncMg2AccListTabulator()");
/* Very heavy!!
	var accInfoArr = mg_getAccListObjArray("_Lists");
	var idOrder = [];
	for(var i=0,len=accInfoArr.length; i<len; i++) {
		idOrder.push(accInfoArr[i]["id"]);
	}
	accListManager.resetOrder(idOrder, {order:true, using:true, clearSelect:true});
*/
	var checkedAccsMap = {};
	if("accs" in selectedAccReg) checkedAccsMap = selectedAccReg["accs"];
	accListManager.changeCheck(checkedAccsMap);
}
function markSelect2Options(elem) {
        if(!elem.id || !("accs" in selectedAccReg)) return;

        $('#' + elem.id + '_parent .select2-results__option').each(function(index, opt) {
		var $optObj = $(opt);
                var matches = opt.id.match(/\-result\-[^\-]*\-(.+)$/);
                if(matches) {
                        var val = matches[1];
			if(val in selectedAccReg["accs"]) {
				 if(!$optObj.hasClass('select2-selected'))$optObj.addClass('select2-selected');
			} else {
				 if($optObj.hasClass('select2-selected')) $optObj.removeClass('select2-selected');
			}
                }
        });
}

function updateSearchSelect() {
	var accInfoArr = mg_getAccListObjArray("_Lists");
	var $select2 = $('#sel_search');
	var optEmpty = new Option("", "", false, false);	// For placeholder
	$select2.find('option').remove().end().append(optEmpty);
	var keys = ['id','name','variety','grp','origin','origin_2','type','other_1','other_2'];
	for(var i=0,iLen=accInfoArr.length; i<iLen; i++) {
		var data = accInfoArr[i];
		var opt = new Option( data.id + "   " + data.name + "   " + data.variety, data.id, false, false);
		var searchvalArr = [];
		for(var j=0,jLen=keys.length; j<jLen ; j++) {
			 searchvalArr.push(data[keys[j]]);
		}
		opt.setAttribute("searchval", searchvalArr.join(';'));	// Add custom attribute for keywork search.
		$select2.append(opt);
	}
	$select2.trigger('change');
}


// Events
function mg_initEvents() {
	window.addEventListener("keydown", mg_keyDownWindow, false);
	window.addEventListener("resize", mg_windowResize, true);
	this.window.addEventListener("keyup", mg_keyUpWindow, false);

//	addCvsEvents();	// Executed after data load in mg_closeLoadingDialog(). Not required here.

	document.getElementById("cvs_scale_div").addEventListener("scroll", function () {
		var left = this.scrollLeft;
		document.getElementById("cvs_view_div").scrollTo(left, 0);
		document.getElementById("cvs_view_scrollbar").scrollTo(left, 0);
	});
	document.getElementById("cvs_view_div").addEventListener("scroll", function () {
		var left = this.scrollLeft;
		document.getElementById("cvs_view_scrollbar").scrollTo(left, 0);
		document.getElementById("cvs_scale_div").scrollTo(left, 0);
	});
	document.getElementById("cvs_view_scrollbar").addEventListener("scroll", function () {
		var left = this.scrollLeft;
		document.getElementById("cvs_view_div").scrollTo(left, 0);
		document.getElementById("cvs_scale_div").scrollTo(left, 0);
	});

	// Popup selecting acc info.
	document.getElementById("div_selecting_acc").addEventListener("mousemove", function (e) {
		if("accs" in this.selectedAccReg && Object.keys(this.selectedAccReg["accs"]).length > 0) {
			$("#div_selecting_acc").css("cursor", "pointer");
		} else {
			$("#div_selecting_acc").css("cursor", "auto");
		}
	}.bind(this));
	document.getElementById("div_selecting_acc").addEventListener("click", function (e) {
		var $divIdlist = $("#div_idlist");
		if($divIdlist.get(0).checkVisibility()) {
			$divIdlist.hide(100);
		} else if("accs" in this.selectedAccReg && Object.keys(this.selectedAccReg["accs"]).length > 0) {
			var bottom = parseInt($("#div_foot_menu").height()) + parseInt($("#div_footer").height());
			$divIdlist.css("bottom", bottom);
			$divIdlist.css("left", $("#div_selecting_acc").offset().left);
			$divIdlist.show(100);
		}
	}.bind(this));
	document.getElementById("div_idlist").addEventListener("mouseleave", function () {
		$(this).hide(100);
	});

        // PCA
	var pcaHandleDefTitle = "Open extended PCA-graph.";
	$("#pca-handle").attr("title", pcaHandleDefTitle);
	document.getElementById("pca-handle").addEventListener("click",  function () {
//		if($('#pca-slide').hasClass('pcs-slide-off')){
		if(!pcaSlideOpened()) {
			$("#div_sup_extgraph").css("display", "none");
			var pca2Width = getPca2Width();
			$('#pca-slide').removeClass('pcs-slide-off').animate({'min-width':pca2Width + 'px', 'max-width':pca2Width + 'px'},200,'swing', function() {
				$('#pca-handle-inner').html("<i class=\"fa fa-chevron-circle-left\" aria-hidden=\"true\"></i>");
			}).addClass('pcs-slide-on');
			$("#pca-handle").attr("title", "Close  extended PCA-graph.");

			// The above .animate() is an asynch-process, so resize afterwards.
			setTimeout(function() {
				$("#td_div_cvs_pca1").trigger("resize");
			}, 210);
		}else{
			$('#pca-slide').removeClass('pcs-slide-on').animate({'min-width':'20px', 'max-width':'20px'},200,'swing', function() {
				$('#pca-handle-inner').html("<i class=\"fa fa-chevron-circle-right\" aria-hidden=\"true\"></i>");
			}).addClass('pcs-slide-off');
			$("#pca-handle").attr("title", pcaHandleDefTitle);
			setTimeout(function() {
				$("#div_sup_extgraph").css("display", "block");
			}, 210);
		}
	});
	document.getElementById("btn_idlist_submit").addEventListener("click",  function () {
		var dbs = getSelectionAccInfoArr().map(function(ref) {return ref.db;});
		if(dbs.length === 0 && !("chr" in selectedAccReg)) return;

		var url = "./?";

		// genome-region.
		if(selectedAccReg["chr"]) {
			url += "chr=" + selectedAccReg["chr"] + "&st=" + selectedAccReg["stPos"] + "&en=" + selectedAccReg["enPos"];
		}

		// Accs.
		if(dbs && dbs.length > 0) {
			var originalAccs = mg_getAccListObjArray("_Lists-origin");
			if(dbs.length < originalAccs.length) {
				url += "&cdb=" + tasukeCompAccArray.encodeUrlSafe(dbs, {compress:true});
			}
		}

		// Move to TASUKE+.
		window.open(url, '_blank', 'noopener=true');	// Open new tab, dont extends storage values.
	});
/*
	document.getElementById("pca_idlist_apply").addEventListener("click",  function () {
		var dbs = JSON.parse(storage.getItem("mg_selectedIds"));
		if(dbs.length == 0) return;

		applyDbs(dbs);
	});
*/

	// Undo/Redo
	document.getElementById("btn_undo_whole").addEventListener("click",  function (e) {
		e.stopPropagation();	// Prevent trigger parent event.
		undoHistory();
	});
	document.getElementById("btn_redo_whole").addEventListener("click",  function (e) {
		e.stopPropagation();	// Prevent trigger parent event.
		redoHistory();
	});
	document.getElementById("btn_deselect_whole").addEventListener("click",  function (e) {
		mg_clearAllSelection();
	});

	// Popup for detail info.
	document.getElementById("graph_popup").addEventListener("mouseleave",  function (e) {
		// In the case of "mouseout", it will fire when "mousein" child element.
		document.getElementById("graph_popup").style["display"] = "none";
	});
}
function addCvsEvents() {
	if(DEBUG_MODE) console.log("Call addCvsEvents()");
	var cvsIdArr = ["cvs_view", "cvs_scale", "cvs_tree", "cvs_list"];
	for(var i=0,len=cvsIdArr.length; i<len; i++) {
		var cvs = document.getElementById(cvsIdArr[i]);
		cvs.addEventListener("mousedown", mg_mouseDownCommon, false);
		cvs.addEventListener("mousemove", mg_mouseMoveCommon, false);
		cvs.addEventListener("mouseup", mg_mouseUpCommon, false);
		cvs.addEventListener("mouseout", mg_mouseOutCommon, false);
		cvs.addEventListener("mousewheel",mg_mouseWheelCommon, false);
	}
}
function removeCvsEvents() {
	if(DEBUG_MODE) console.log("Call removeCvsEvents()");
	var cvsIdArr = ["cvs_view", "cvs_scale", "cvs_tree", "cvs_list"];
	for(var i=0,len=cvsIdArr.length; i<len; i++) {
		var cvs = document.getElementById(cvsIdArr[i]);
		cvs.removeEventListener("mousedown", mg_mouseDownCommon, false);
		cvs.removeEventListener("mousemove", mg_mouseMoveCommon, false);
		cvs.removeEventListener("mouseup", mg_mouseUpCommon, false);
		cvs.removeEventListener("mouseout", mg_mouseOutCommon, false);
		cvs.removeEventListener("mousewheel",mg_mouseWheelCommon, false);
	}
}

function mg_keyDownWindow(e) {
//	if(e.keyCode === 27) e.preventDefault();
	if (e.repeat) return;

	keyState[e.keyCode] = true;
	if(DEBUG_MODE) console.log("keyDown:" + e.keyCode); 

	if(e.keyCode === 27) {
		if(validDrugPos()) {
			mg_disableDrug();
		} else {
			prevCanvasImage();
		}
	}
}
function mg_windowResize(e) {
	adjustViewScrollbar();
}
/*
var timeoutWindowResize = null;
function mg_windowResize(e) {
	clearTimeout(timeoutWindowResize);
	timeoutWindowResize = setTimeout(function() {
		adjustViewScrollbar();
		timeoutWindowResize = null;
	}, 20);
}
*/
function mg_keyUpWindow(e) {
	if (e.repeat) return;
	delete keyState[e.keyCode];
	if(DEBUG_MODE) console.log("keyUp:" + e.keyCode);
}

var XY = {chrreg:null, dbs:[], x1:null, y1:null, x2:null, y2:null, lastX:null, lastY:null};
function mg_mouseDownCommon(e) {
	e.preventDefault();

	var cvs = this;
	var ctx = cvs.getContext("2d");

	var rect = e.target.getBoundingClientRect();
	//TODO isSmartPhone()
	var rightclick;
	if(e.which) {
		rightclick = (e.which==3);
	} else if(e.button) {
		rightclick = (e.button==2);
	}
	if(!rightclick) {
		var blockW = BLOCKW;
//		var blockW = parseInt(storage.getItem("mg_blockW"));
		XY.x1 = parseInt(e.clientX)-parseInt(rect.left);
		XY.y1 = parseInt(e.clientY)-parseInt(rect.top);
		XY.x2 = XY.x1;
		XY.y2 = XY.y1;
		XY.chrreg = scaleGenomes.getChrreg(XY.x1);
//		XY.chrreg = block2chrreg[Math.floor(XY.x1 / blockW)];
//console.log(XY);
	}
}
function mg_mouseMoveCommon(e) {
	e.preventDefault();

	if(timeoutDispPopup) clearTimeout(timeoutDispPopup);

	var cvs = this;
	var rect = e.target.getBoundingClientRect();
	var x2 = parseInt(e.clientX)-parseInt(rect.left);
	var y2 = parseInt(e.clientY)-parseInt(rect.top);
	if(XY.x1 !== null) {
		XY.x2 = x2;
		XY.y2 = y2;
	}
	XY.lastX = x2;
	XY.lastY = y2;
	drawCanvasSelection(cvs, XY);
}
function mg_mouseWheelCommon(e) {
	if(timeoutDispPopup) clearTimeout(timeoutDispPopup);

//	var $graphPopup = $('#graph_popup');
//	if($graphPopup.is(":visible")) $graphPopup.css("display", "none");
}

function getBlockAccs(yt, yb) {
	var blockH = parseInt($("#sel_blockh").val());
	var stBlockY = Math.floor(yt / blockH);
	var enBlockY = Math.floor(yb / blockH);
	var accOrder = mg_getAccListObjArray("_Lists");
	var targetAccs = [];
	for(var i=stBlockY; i<=enBlockY; i++) {
		if(accOrder[i]) targetAccs.push(accOrder[i]);
	}
	return targetAccs;
}
function getBlockPos(XY) {
	var viewMode = $("#sel_viewmode").val();
	var blockW = BLOCKW;
//	var blockW = parseInt(storage.getItem("mg_blockW"));
	var blockH = parseInt($("#sel_blockh").val());
	var zoom = BLOCKSIZE;
//	var zoom = parseInt(storage.getItem("mg_zoom"));

	var chr, xl, xr;
	var chrStPos = null;
	var chrEnPos = null;
	var lastChrPos = null;
//	var lastChrreg = null;
//	var lastChrStX = null;
//	var lastChrPos = null;
	if(viewMode === "admixture") {
		xl = XY.x1;
		xr = XY.x2;
		if(xl > xr) [xl, xr] = [xr, xl];
	} else {
		var arr = scaleGenomes.getChrPos(XY);
		xl = arr[0]; xr = arr[1]; chr = arr[2]; chrStPos = arr[3]; chrEnPos = arr[4]; lastChrPos = arr[5];
	}

	// For Vertical.
	var yt, yb;
	if(XY.y1 === XY.y2) {
		yt = Math.floor(XY.y1/blockH)*blockH + 1;
		yb = yt + blockH - 2;
	} else if(XY.y1 < XY.y2) {
		yt = Math.floor(XY.y1/blockH)*blockH + 1;
		yb = Math.ceil(XY.y2/blockH)*blockH - 1;
	} else {
		yt = Math.floor(XY.y2/blockH)*blockH + 1;
		yb = Math.ceil(XY.y1/blockH)*blockH - 1;
	}

	return [xl, xr, yt, yb, chr, chrStPos, chrEnPos, lastChrPos];
}
function mg_mouseUpCommon(e) {
	e.preventDefault();

	while("getContext" in this && XY.x1 !== null) {
		var act = "";	// Clear -> reselect
		if("16" in keyState && "18" in keyState) {
			act = "common";
		} else if("18" in keyState) {
			act = "remove";
		} else if("16" in keyState || "17" in keyState) {
			act = "add";
		}

		var cvs = this;
		var cvsId = cvs.getAttribute("id");
		var viewMode = $("#sel_viewmode").val();
		if(viewMode === "admixture" && cvsId === "cvs_scale") break;

		var rect = e.target.getBoundingClientRect();
		if(e.offsetX > 0 && parseInt($(event.currentTarget).width())){
			XY.x2 = parseInt(e.clientX)-parseInt(rect.left);
			XY.y2 = parseInt(e.clientY)-parseInt(rect.top);
		}
		var cnvpos = getBlockPos(XY);
		var xl = cnvpos[0];
		var xr = cnvpos[1];
		var yt = cnvpos[2];
		var yb = cnvpos[3];
		var chr = cnvpos[4];
		var stPos = cnvpos[5];
		var enPos = cnvpos[6];

		var onRegSel = onRegionSelect(viewMode, cvsId);

		if(onRegSel && (cvsId === "cvs_view" || cvsId === "cvs_scale")) {
			if(xl === xr) {
				// Deselect genome-region.
				delete selectedAccReg["chr"];
				delete selectedAccReg["stPos"];
				delete selectedAccReg["enPos"];
			} else {
				selectedAccReg["chr"] = chr;
				selectedAccReg["stPos"] = stPos;
				selectedAccReg["enPos"] = enPos;
			}
		}

		if(cvsId !== "cvs_scale") {
			var bufAccs = selectedAccReg["accs"];
			if(!act || act === "common") selectedAccReg["accs"] = {};
			var targetAccs = getBlockAccs(yt, yb);
			var lenTargetAccs = targetAccs.length;
			if(lenTargetAccs>0) {
				if( !("accs" in selectedAccReg) ) selectedAccReg["accs"] = {};
				for(var i=0; i<lenTargetAccs; i++) {
					if(act === "common") {
						if(bufAccs[targetAccs[i]["id"]]) selectedAccReg["accs"][targetAccs[i]["id"]] = true; 
					} else if(act === "remove" || (lenTargetAccs === 1 && targetAccs[0].id in selectedAccReg["accs"] && act === "add")) {
						delete selectedAccReg["accs"][targetAccs[i]["id"]];
					} else {
						selectedAccReg["accs"][targetAccs[i]["id"]] = true;
					}
				}
				addHistory();
			}
			bufAccs = null;
/*
			if( !("16" in keyState || "17" in keyState || "18" in keyState) ) selectedAccReg["accs"] = {};
			var targetAccs = getBlockAccs(yt, yb);
			var lenTargetAccs = targetAccs.length;
			if(lenTargetAccs>0) {
				if( !("accs" in selectedAccReg) ) selectedAccReg["accs"] = {};
				for(var i=0; i<lenTargetAccs; i++) {
					if("18" in keyState || (lenTargetAccs === 1 && targetAccs[0].id in selectedAccReg["accs"] && ("16" in keyState || "17" in keyState))) {
						delete selectedAccReg["accs"][targetAccs[i]["id"]];
					} else {
						selectedAccReg["accs"][targetAccs[i]["id"]] = true;
					}
				}
				addHistory();
			}
*/
		}
		drawViewSelected();
		updateSelectedIdsForm();
		if($("#div_select_idlist").is(":visible")) syncMg2AccListTabulator();

		break;
	}
	initDrugPos();
}
function  mg_mouseOutCommon(e) {
	if(timeoutDispPopup) clearTimeout(timeoutDispPopup);	// Cancel popup display.
	mg_mouseUpCommon(e);
	$("#span_footer_text").text("");
	prevCanvasImage();
}

function getMapClone(map) {
	if(!map) return {};
	var keys = Object.keys(map);
	var newMap = {};
	for(var i=0,len=keys.length; i<len; i++) newMap[keys[i]] = map[keys[i]];
	return newMap;
}
function addHistory() {
	var his = selectedAccReg["accs"];
	if(!his) his = {};
	var lenUndoBuff = undoBuff.length;

	// If it's the same as last time, don't add history.
	if(lenUndoBuff > 0) {
		var lastHis = undoBuff[lenUndoBuff-1];
		var lastIds = Object.keys(lastHis);
		var newIds = Object.keys(his);
		var lenLastIds = lastIds.length;
		var lenNewIds = newIds.length;
		var diff = false;
		if(lenLastIds === lenNewIds) {
			if(lenLastIds === 0) {
				// Contiguous empty selection. skip.
			} else {
				for(var i=0; i<lenLastIds; i++) {
					if( !(lastIds[i] in his) ) { diff = true; break; }
				}
			}
		} else {
			diff = true;
		}
		if(!diff) return;
	}

	if(DEBUG_MODE) console.log("Exec macrogenome.addHistory()");

	if(lenUndoBuff >= maxUndoBuff) undoBuff.shift();
	undoBuff.push(getMapClone(his));
	redoBuff.splice(0);	// Empty redo history.
}
function undoHistory() {
	var redoBuf = undoBuff.pop();
	if(!redoBuf) return;
	var buf = undoBuff[undoBuff.length-1];
	if(!buf) return;	// 

	if(DEBUG_MODE) console.log("Exec macrogenome.undoHistory()");
	redoBuff.push(getMapClone(redoBuf));
	applyHistory(getMapClone(buf));
//	applyHistory(buf);
}
function redoHistory() {
	var buf = redoBuff.pop();
	if(!buf) return;

	if(DEBUG_MODE) console.log("Exec macrogenome.redoHistory()");
	undoBuff.push(getMapClone(buf));
	applyHistory(getMapClone(buf));
//	applyHistory(buf);
}
function applyHistory(accMap) {
	selectedAccReg["accs"] = accMap;
	reflectSelectionWhole();
//	drawViewSelected();
//	updateSelectedIdsForm(); 
}
function clearHistory() {
	undoBuff.splice(0);
	this.redoBuff.splice(0);
	undoBuff.push({});
}

function adjustViewScrollbar() {
	var $cvs = $("#cvs_view");
	var $cvsDiv = $cvs.parent();
	var $bar = $("#cvs_view_scrollbar");
	var $mainViewport = $("#main_view_div");
	var $barInner = $("#cvs_view_scrollbar_inner");

	// Scrollbar
	if($cvs.width() > $cvsDiv.width()) {
		$bar.css("display", "block");
		$barInner.width($cvs.width());
		$bar.width($cvsDiv.width());
	
		var barTop = $mainViewport.offset().top + $mainViewport.height();
//console.log("barTop:" + barTop + " <= " + $mainViewport.offset().top + "," + $mainViewport.height() + $bar.height());
		$bar.offset({top:barTop, left:$cvsDiv.offset().left});
	} else {
		$bar.css("display", "none");
	}

	// Genome scalebar
	$cvsScaleDiv = $("#cvs_scale_div");
	$cvsScaleDiv.width($cvsDiv.width());
	var cvsScaleDivLeft = $("#cvs_tree").width() + $("#cvs_list").width();
	$cvsScaleDiv.offset({left:$cvsDiv.offset().left});

/*
	// Heatmap scale
	$cvsHmScale = $("#cvs_hm_scale");
	if($cvsHmScale.get(0).getAttribute("height") > $cvs.height()) {
		$cvsHmScale.height($cvs.height());
		$cvsHmScale.get(0).setAttribute("height", $cvs.height());
	}
//	if($cvsHmScale.height() > $cvs.height()) $cvsHmScale.height($cvs.height());
*/
}
function validDrugPos() {
	return XY.x1 !== null;
}
function initDrugPos() {
	XY.chrreg = null;
	XY.x1 = null;
	XY.y1 = null;
}
function mg_disableDrug() {
	initDrugPos();
//	var ctx = document.getElementById("cvs_view").getContext("2d");
//	if(bufViewImage) ctx.putImageData(bufViewImage, 0, 0);
	prevCanvasImage();
}

function mg_createSystemTreeObj(cvsId) {
	var treeobj = new PhyloTreeCreater(document.getElementById(cvsId));
	treeobj.tMargin = -3;
	treeobj.bMargin = 0;
	treeobj.rMargin = 0;
	treeobj.lineWidth = 1;
	treeobj.auxLineColor = "rgba(255,255,255,0.35)";
	mg_initSystemTreeStyle(treeobj);
	treeobj.treeWidth = storage.getItem("_SystemTreeW");
	return treeobj;
}
function mg_initSystemTreeStyle(treeobj) {
	treeobj.headerString = "Whole genome tree";
	treeobj.lineColor = 'black';
}
function mg_clearSystemTreeCache() {
	storage.setItem("_SysTreeNewick", "");
	storage.setItem("mg_systemTreeCollapsedBranch", "{}");
	storage.setItem("mg_systemTreeLastLeafOrder", "[]");
}
function mg_drawSystemTree(treeobj) {
	if(!treeobj) return;

	var nwk = storage.getItem("_SysTreeNewick");
	treeobj.parseNewick(nwk);
	var accDb2ObjMap = mg_getAccDb2ObjMap();
	var idNames = [];
	for(var i=0,len=treeobj.order.length; i<len; i++) {
		idNames.push(accDb2ObjMap[treeobj.order[i].childs].id);
	}
	treeobj.changeLeafName(idNames);
	mg_initSystemTreeStyle(treeobj);
	var w = parseInt(storage.getItem("_SystemTreeW"));
	treeobj.treeWidth = w;
	treeobj.rowHeight = parseInt($("#sel_blockh").val());
	treeobj.clear();
	treeobj.type = storage.getItem("_SystemTreeMode");
	treeobj.correctMethod = storage.getItem("_SystemTreeCorrect");
	var type = storage.getItem("_SystemTreeSortType");	
	var xscend = storage.getItem("_SystemTreeSort");
	if(xscend !== "") treeobj.sortByLength(xscend, type);
	treeobj.adjustCanvasSizeAuto();
	var mode = storage.getItem('_CurrentSystemTreeMode');
	var phyNewick = storage.getItem("_PhylipTreeNewick");
	if(phyNewick) {
		treeobj.lineColor = 'orange';
	} else if(mode === 'newick') {
		treeobj.lineColor = '#705969';
	} else {
		treeobj.lineColor = 'black';
	}
	treeobj.draw();

	bufTreeImageOri = treeobj.ctx.getImageData(0, 0, treeobj.cvs.width, treeobj.cvs.height);
	bufTreeImage = bufTreeImageOri;
}
function mg_drawTitle() {
	var en = JSON.parse(storage.getItem("_Env"));

	var grpCol = null;
	if(storage.getItem("_GrpCol")) grpCol = JSON.parse((storage.getItem("_GrpCol")));

	var accInfos = mg_getAccListObjArray("_Lists");
	var cvs = document.getElementById("cvs_list");
	var blockH = parseInt($("#sel_blockh").val());
	var w = parseInt(storage.getItem("_TitleW"));
	var h = blockH * accInfos.length;
	cvs.width = w;
	cvs.height = h;
	cvs.setAttribute("width", w);
	cvs.setAttribute("height", h);
	var ctx = cvs.getContext("2d");
	var fontSize = blockH + 2;
	if(fontSize > 11) fontSize= 11;
	ctx.font = fontSize +"px 'Courier'";
	ctx.textAlign = "left";
	ctx.textBaseline = "top";
	var titleType = storage.getItem("_Name");
	var subType = storage.getItem("_NameSub");
	if(subType == "subvariety") {subType = "grp";}
	for(var i=0,len=accInfos.length; i<len; i++) {
		var accInfo = accInfos[i];
		var y = i * blockH;
//console.log((i*blockH) + " " + accInfo["id"]);
		if(storage.getItem("_ColorCode") === "on" && grpCol) {
			ctx.fillStyle = grpCol[accInfo[subType]];
		} else {
			ctx.fillStyle = "black";
		} 
		ctx.fillText(accInfo[titleType], 3, y);
		if(subType) ctx.fillText(accInfo[subType], 90, y);
	}
	bufTitleImageOri = ctx.getImageData(0, 0, w, h);
	bufTitleImage = bufTitleImageOri;
}
function syncList2Tree2List() {
	mg_drawSystemTree(mainTreeobj);
	var accOrder = mainTreeobj.getNameOrder();
	var id2obj = mg_getAccId2ObjMap();

	var accInfoArr = [];
	for(var i=0,len=accOrder.length; i<len; i++) {
		accInfoArr.push(id2obj[accOrder[i]]);
	}
	mg_setAccListObjArray("_Lists", accInfoArr);

/* Do not setItem("_Lists") directly, as this requires updating the acc2IndexMap.
	var dbs = [];
	for(var i=0,len=accOrder.length; i<len; i++) {
		dbs.push(id2obj[accOrder[i]]["db"]);
	}
        var accDb2ObjMap = mg_getAccDb2ObjMap();
	storage.setItem("_Lists", dbs.join(","));
*/
}

function getAndDrawVaraintDencity() {
	var chrInfos = JSON.parse(storage.getItem("_Chrs"));
	var accs = mg_getAccListObjArray("_Lists-origin").length;

	 var expectedDataSize = 0;	// For progress bar.

	if(variantDensInfoOri["varcount"]) {
		for(var i=0,len=chrInfos.length; i<len; i++) {
			expectedDataSize += Math.ceil(chrInfos[i].size / BLOCKSIZE);
		}
		// (chrTotal * accs * byte * base64increase) + (accs * JSON-syntax) + (ChrStat)
		expectedDataSize = (expectedDataSize * accs * 4 * 1.33) + (accs * 80) + (chrInfos.length * 15);
	}

	//TODO if kmers is empty
	if(variantDensInfoOri["admixture"]) {
		var kmers = JSON.parse(storage.getItem("mg_kmers"));
		for(var i=0,len=kmers.length; i<len; i++) {
			expectedDataSize += accs * (kmers[i] * FLOAT_BYTE * 1.33 + 15) + 40;
		}
	}

	if(variantDensInfoOri["pca"]) {
		var pcs =  parseInt(storage.getItem("mg_pcs"));
		expectedDataSize += (pcs * accs * 4 * 1.33) + (accs * 40);
	}

//	mg_showLoadingDialog();
	$.ajax({
		url: "./bin/getMacrogenome.php",
		type: "POST",
		async:true,
		data: {
//			"prm_Zoom": zm
//			"prm_Chrs": targetChr
		},
		xhr: function () {
			var xhr = $.ajaxSettings.xhr();
			xhr.onprogress = function(evt) {
				if(evt) {
					if(DEBUG_MODE) console.log("getMacrogenome.php loaded:" + evt.loaded + " / " + expectedDataSize);
					mg_updateProgress(evt.loaded / expectedDataSize * 100);
				}
			};
			xhr.onloadend = function(evt) {
				mg_updateProgress(100);
			}
			return xhr;
		}
	}).done(function(data) {
		if(DEBUG_MODE) console.log("getAndDrawVaraintDencity: json_decode&replication start");
		var wholeData =  JSON.parse(data);

		// Varaint count
		if("varcount" in wholeData && wholeData["varcount"]) {
			// Global var. At this point the data is encoded, but will later be replaced with the decoded data.
			variantDensInfoOri = wholeData["varcount"];

			var densAll = variantDensInfoOri["data"];
			var chrStat = variantDensInfoOri["stat"];
			var accInfos = mg_getAccListObjArray("_Lists-origin");

			// Save JSON structure as json other than variant number.
			var db2chr2bin = {};
			var dbs = Object.keys(densAll);
			for(var i=0,iLen=dbs.length; i<iLen; i++) {
				var db = dbs[i];
				db2chr2bin[db] = {};
				for(var j=0,jLen=densAll[db].length; j<jLen; j++) {
					var chr = densAll[db][j]["chr"];
					db2chr2bin[db][chr] = densAll[db][j]["b64"];
					delete densAll[db][j]["b64"];	// For reduce memory.
				}
			}
			variantDensInfoTempJson = JSON.stringify(variantDensInfoOri);	// Binary data is deleted.

			if(DEBUG_MODE) console.log("getAndDrawVaraintDencity: parse start");
			// Decode binary data.
			for(var i=0,iLen=dbs.length; i<iLen; i++) {
				var densAcc = densAll[dbs[i]];
				for(var j=0,jLen=densAcc.length; j<jLen; j++) {
					var chr = densAcc[j]["chr"];
					var densBinStr = atob(db2chr2bin[dbs[i]][chr]);
					delete db2chr2bin[dbs[i]][chr];	// For reduce memory.
					densAcc[j]["dens_arr"] = [];
					densAcc[j]["eff_arr"] = [];
					for(var k=0,kLen=densBinStr.length; k<kLen; k+=4) {
						var bin8 = binstrToUint8array(densBinStr.substr(k, 2));
						var bin16 = new Uint16Array(bin8.buffer);
						var varCnt = Array.from(bin16)[0];
						densAcc[j]["dens_arr"].push(varCnt);

						bin8 = binstrToUint8array(densBinStr.substr(k+2, 2));
						bin16 = new Uint16Array(bin8.buffer);
						var varEff = Array.from(bin16)[0];
						if(varCnt > 0) {
							densAcc[j]["eff_arr"].push(varEff);
//							densAcc[j]["eff_arr"].push(varEff / varCnt);
						} else {
							 densAcc[j]["eff_arr"].push(0);
						}
					}
				}
			}
			if(DEBUG_MODE) console.log("getAndDrawVaraintDencity: parse end");
		} else {
			setDummyVariantCount();
		}
		scaleGenomes = new genomesScale(document.getElementById("cvs_scale"));	// Global var.

		// Admixure
		if("admixture" in wholeData && wholeData["admixture"]) {
			admixtureInfoOri = wholeData["admixture"];	// Global var.
			var dbs = Object.keys(admixtureInfoOri["data"]);
			var kmers = admixtureInfoOri["stat"]["kmers"];
			var kmerCols =  admixtureInfoOri["stat"]["cols"];

			for(var i=0,iLen=dbs.length; i<iLen; i++) {
				var db = dbs[i];
				var binPos = 0;
				var binStr = atob(admixtureInfoOri["data"][db]["b64"]);
				for(var j=0,jLen=kmers.length; j<jLen; j++) {
					var kmer = kmers[j];
					admixtureInfoOri["data"][db][kmer] = [];
					var cols = kmerCols[j];
					for(var k=0; k<cols; k++) {
						var bin8 = binstrToUint8array(binStr.substr(binPos, FLOAT_BYTE));
						var bin32 = new Float32Array(bin8.buffer);
						var val = parseFloat(Array.from(bin32)[0]);
						if(val < 0) {
							admixtureInfoOri["data"][db][kmer] = null;
							binPos += FLOAT_BYTE * cols;
							break;
						}
						admixtureInfoOri["data"][db][kmer].push(val);
						binPos += FLOAT_BYTE;
					}
//console.log("Test:" + db + "->" + mg_getAccDb2ObjMap()[db].id + "," + kmer + "," + JSON.stringify(admixtureInfoOri["data"][db][kmer]));
				}
				delete admixtureInfoOri["data"][db]["b64"];	// For reduce memory;
			}
		}
		scaleAdmixture = new admixtureScale(document.getElementById("cvs_scale"));	// Global var.

		// PCA
		if("pca" in wholeData && wholeData["pca"]) {
			pcaInfoOri = wholeData["pca"];	// Global var.
			var dbs = pcaInfoOri["stat"]["dbOrder"];
			var pcs = parseInt(pcaInfoOri["stat"]["pcs"]);
			var binPos = 0;
			var binStr = atob(pcaInfoOri["data"]["b64"]);
			delete pcaInfoOri["data"]["b64"];	 // For reduce memory;
			for(var i=0,iLen=dbs.length; i<iLen; i++) {
				var db = dbs[i]["db"];
				var accInfo = {fid:dbs[i]["fid"], vec_arr:[]};
				for(var j=0; j<pcs; j++) {
					var bin8 = binstrToUint8array(binStr.substr(binPos, FLOAT_BYTE));
					var bin32 = new Float32Array(bin8.buffer);
					var val = parseFloat(Array.from(bin32)[0]);
					accInfo["vec_arr"].push(val);
					binPos += FLOAT_BYTE;
				}
				pcaInfoOri["data"][db] = accInfo;
			}
//console.log(pcaInfoOri);
			if(pcaFlg) initPca(); 
		}
		initAccListTabulator();
//		syncMg2AccListTabulator();	// Not required here (run when opening dialog).
		drawChangeZoomFactory();
	}).fail(function() {
	}).always(function() {
//		mg_closeLoadingDialog();
	});
}
function setDummyVariantCount() {
	var accInfos = mg_getAccListObjArray("_Lists");
	var chrInfos = JSON.parse(storage.getItem("_Chrs"));
	BLOCKSIZE = 100000;
//	var blockW =  100000;
//	storage.setItem("mg_blockW", blockW);

	var readChr = false;
//console.log(variantDensInfoOri);
	variantDensInfoOri = {data:{}, stat:{blockSize:BLOCKSIZE, stat:{}}};
	for(var i=0,iLen=accInfos.length; i<iLen; i++) {
		var db = accInfos[i]['db'];
		variantDensInfoOri['data'][db] = [];
		for(var j=0,jLen=chrInfos.length; j<jLen; j++) {
			var blockNum = Math.ceil(parseInt(chrInfos[j]['size']) / BLOCKSIZE);
			if(!readChr)  variantDensInfoOri['stat']['stat'][chrInfos[j]['id']] = blockNum;
			variantDensInfoOri['data'][db].push({chr:chrInfos[j]['id']});
		}
		readChr = true;
	}
	variantDensInfoTempJson = JSON.stringify(variantDensInfoOri);

	for(var i=0,iLen=accInfos.length; i<iLen; i++) {
		var db = accInfos[i]['db'];
		for(var j=0,jLen=chrInfos.length; j<jLen; j++) {
			var chr = chrInfos[j]['id'];
			var blockNum = variantDensInfoOri['stat']['stat'][chr];
			variantDensInfoOri['data'][db][j]['dens_arr'] = [];
			for(var k=0; k<blockNum; k++) {
				  variantDensInfoOri['data'][db][j]['dens_arr'][k] = 0;
			}
		}
	}
//console.log(variantDensInfoOri);
}

function recountVaraint(arrkey="dens_arr") {
/*
	var mode = $("#sel_viewmode").val();
	var arrkey = "dens_arr";
	if(mode === "snpeff") arrkey = "eff_arr";
*/
	if(DEBUG_MODE) console.log("VariantCountData: recount start");
	var blockSize = BLOCKSIZE;
	var srcDensAll = variantDensInfoOri["data"];
	var srcChrStat = variantDensInfoOri["stat"];
	var margeBlocks = Math.ceil(blockSize / srcChrStat["blockSize"]);

	resInfo = JSON.parse(variantDensInfoTempJson);
	var dstDensAll = resInfo["data"];
	var dstChrStat = resInfo["stat"];	
	var chrBlocks = {};
	var varCntMap = {};
	var db2chr2densArr = {};
	var minWhole = blockSize * 3;
	var maxWhole = 0;
	var totalWhole = 0;
	var blocksWhole = 0;
	var dbs = Object.keys(srcDensAll);
	for(var i=0,iLen=dbs.length; i<iLen; i++) {
		var srcDensAcc = srcDensAll[dbs[i]];
		db2chr2densArr[dbs[i]] = {};
		for(var j=0,jLen=srcDensAcc.length; j<jLen; j++) {
			var blocks = 0;
			var min = blockSize * 3;
			var max = 0;
			var total = 0;

			dstDensAll[dbs[i]][j]["dens_arr"] = [];
			var srcArr = srcDensAcc[j][arrkey];
			var dstArr = dstDensAll[dbs[i]][j]["dens_arr"];
			for(var k=0,kLen=srcArr.length; k<kLen;) {
				var varCnt = 0;
				for(l=0; l<margeBlocks && k<kLen; l++,k++) {
					varCnt += srcArr[k];
				}
//				if(arrkey === "eff_arr") varCnt = varCnt / margeBlocks;
				dstArr.push(varCnt);
				if(min > varCnt) min = varCnt;
				if(max < varCnt) max = varCnt;
				total += varCnt;
				blocks++;

				var intVarCnt = Math.round(varCnt);
				if( !(intVarCnt in varCntMap) ) varCntMap[intVarCnt] = 0;
				varCntMap[intVarCnt]++;
			}
			var dstDensAccChr = dstDensAll[dbs[i]][j];
			db2chr2densArr[dbs[i]][dstDensAccChr["chr"]] = dstArr;

			dstDensAccChr["min"] = min;
			dstDensAccChr["max"] = max;
			dstDensAccChr["total"] = total;
			dstDensAccChr["avg"] = Math.round(total / blocks);

			if(minWhole > min) minWhole = min;
			if(maxWhole < max) maxWhole = max;
			totalWhole += total;
			blocksWhole += blocks;
			if( !(dstDensAccChr["chr"] in chrBlocks) ) chrBlocks[dstDensAccChr["chr"]] = blocks;
		}
	}

	// Statictics
//	if(arrkey === "dens_arr") {
		var maxCutoffBlocks = blocksWhole / 100;
		var crtCutoffBlocks = 0;
		var medBlocks = (blocksWhole - maxCutoffBlocks) / 2;
		var medVarCnt = -1;
		var maxAdjust = -1;
		var avgTotal = 0;
		var avgBlocks = 0;
		var varCntMapKeys = Object.keys(varCntMap);
		varCntMapKeys.sort(function(a,b) {
			return parseInt(b) - parseInt(a);	// Sort Descending
		});
		for(var i=0,len=varCntMapKeys.length; i<len; i++) {
			var varCnt = parseInt(varCntMapKeys[i]);
			var varBlocks  = parseInt(varCntMap[varCnt]);
			crtCutoffBlocks += varBlocks;
			if(medVarCnt < 0 && crtCutoffBlocks > medBlocks) medVarCnt = varCnt;
			if(crtCutoffBlocks > maxCutoffBlocks) {
				if(maxAdjust < 0) maxAdjust = varCnt;
				avgTotal += parseInt(varCnt) * varBlocks;
				avgBlocks += varBlocks;
			}
		}
		dstChrStat["maxAdjust"] = maxAdjust;
		dstChrStat["avg"] = Math.round(avgTotal / avgBlocks);
		dstChrStat["med"] = medVarCnt;
/*
	} else {
		dstChrStat["maxAdjust"] = maxWhole;
		dstChrStat["avg"] = totalWhole / blocksWhole;	
		dstChrStat["med"] =  dstChrStat["avg"];	//TODO
	}
*/
	dstChrStat["blockSize"] = blockSize;	// overwrite
	dstChrStat["stat"] = chrBlocks;	// overwrite
	dstChrStat["min"] = minWhole;
	dstChrStat["max"] = maxWhole;
	dstChrStat["db2chr2densArr"] = db2chr2densArr;
	if(DEBUG_MODE) console.log("VariantCountData: recount end");
	if(DEBUG_MODE) console.log(resInfo["stat"]);
//console.log(resInfo);
	return resInfo;
}

function mg_updateView() {
	adjustBlockHForFirefox();

	syncList2Tree2List();
	mg_drawTitle();
	drawChangeZoomFactory();
}

//var blockW = 0;
//var chrnum2chr = {};            // To reduce memory usage, chr name is treated as integer instead of string.
//var chrnum2BlockDiv = {};       // [ChrStartBlock, nextChrStartBlock]
//var block2chrreg = [];  // block2chrreg[wholeBlock] = [chrnum, chrBlock]
function initCvsViewCache() {
	scaleGenomes.init();
	scaleAdmixture.init();
/*
	chrnum2chr = {};
	chrnum2BlockDiv = {};
	block2chrreg = [];
*/
	bufViewImage = null;
}
function getTargetChrInfos() {
	var chrInfosPre = JSON.parse(storage.getItem("_Chrs"));
	var targetChrs = $("#sel_chr").val();	// Always array.
	if(DEBUG_MODE) console.log("Change chrs:" + targetChrs);
	var chrInfos = [];

	var chrId2Obj = {};
	for(var i=0,len=chrInfosPre.length; i<len; i++) {
		chrId2Obj[chrInfosPre[i].id] = chrInfosPre[i];
	}
	for(var i=0,len=targetChrs.length; i<len; i++) {
		chrInfos.push(chrId2Obj[targetChrs[i]]);
	}
/*
	if(targetChrs) {
		for(var i=0,len=chrInfosPre.length; i<len; i++) {
			if(chrInfosPre[i].id === targetChr) chrInfos.push(chrInfosPre[i]);
		}
	} else {
		chrInfos = chrInfosPre;
	}
*/
	return chrInfos;
}
function setMenu() {
	// Acc/Region selection
	changeColor($('#top_menu .allHighlight'), (("chr" in selectedAccReg) || ("accs" in selectedAccReg && Object.keys(selectedAccReg["accs"]).length)));
	changeColor($('#top_menu .regHighlight'), ("chr" in selectedAccReg));
	changeColor($('#top_menu .accHighlight'), ("accs" in selectedAccReg && Object.keys(selectedAccReg["accs"]).length));

	if(storage.getItem("_popupDetail") == 1) {
		$("#top_menu .menuPopupDetail i").removeClass("fa-circle-o").addClass("fa-circle");
	} else {
		$("#top_menu .menuPopupDetail i").removeClass("fa-circle").addClass("fa-circle-o");
	}
}
function changeColor(elem, isValid) {
	if(isValid) {
		elem.css("color", "black");
	} else {
		elem.css("color", "#aaaaaa");
	}
}
function changePopupDetail() {
	var flgPre = storage.getItem("_popupDetail");
	var flg;
	flg = (flgPre == 0) ? 1 : 0;
	storage.setItem("_popupDetail", flg);

	if(graphPcaA) graphPcaA.popupDetail = (flg===1) ? true : false;
	if(graphPcaB) graphPcaB.popupDetail = (flg===1) ? true : false;

	setMenu();
}

function initPca() {
	var en = JSON.parse(storage.getItem("_Env"));
	var cvsW = DEF_PCA_GRAPH_WIDTH;
	var cvsH = DEF_PCA_GRAPH_HEIGHT;
	if("pcaGraphW" in en && en["pcaGraphW"]) cvsW = en["pcaGraphW"];
	if("pcaGraphH" in en && en["pcaGraphH"]) cvsH = en["pcaGraphH"];	
//	var cvsW = 420;
//	var cvsH = 460;

	var legendW = 80;
	
	graphPcaA = new tasukeGraphManager(document.getElementById("cvs_pca1"));	// Global var
	graphPcaB = new tasukeGraphManager(document.getElementById("cvs_pca2"));	// Global var

	var labelArr = [["variety", "Variety"],["grp","Sub variety"], ["origin", "Origin"], ["origin_2", "Origin2"],
		["type", "Type"], ["other1", "Other1"], ["other_2", "Other2"]];
	var labelArrWithName = [["fid", "FID"], ["id", "ID"], ["name", "Name"]].concat(labelArr);

	var objs = [[graphPcaA, "1"], [graphPcaB, "2"]];
	for(var k=0,kLen=objs.length; k<kLen; k++) {
		var obj = objs[k][0];
		var objNum =  objs[k][1];

		obj.DEBUG_MODE = DEBUG_MODE;
		obj.popupTimelag = DEF_POPUP_TIMELAG;
		obj.initCvsSize(cvsW, cvsH);
		obj.initZoomForm("sel_zoom_pca" + objNum);
		if(pcaInfoOri) {
			var $sel_pca_y = $("#sel_pca" + objNum + "_y");	
			var $sel_pca_x = $("#sel_pca" + objNum + "_x");
			var sels_pca = [$sel_pca_y, $sel_pca_x];
			var sumVal = 0;
			if(pcaInfoOri["stat"]["val"]) {
				for(var l=0,lLen=pcaInfoOri["stat"]["val"].length; l<lLen; l++) {
					 sumVal += pcaInfoOri["stat"]["val"][l];
				} 
			}
			var pcs = pcaInfoOri["stat"]["pcs"];
			for(var i=0,iLen=sels_pca.length; i<iLen; i++) {
				for(var j=0; j<pcs; j++) {
					var optLabel = "PC" + (j+1);
					if(pcaInfoOri["stat"]["val"]) {
						var contRate =  pcaInfoOri["stat"]["val"][j] / sumVal * 100;
						optLabel += " (" + contRate.toFixed(2) + "%)";
					}
					var opt = new Option(optLabel, j, false, false);
					sels_pca[i].append(opt);
				}
			}
			
		}
//		obj.initCountForm("spn_pca" + objNum + "_totalcnt", "spn_pca" + objNum + "_selcnt");
		obj.initUndoRedo("btn_undo_pca" + (k+1),  "btn_redo_pca" + (k+1));

		// Label
		var $selLabel = $("#sel_label_pca" + objNum);
		for(var j=0,jLen=labelArrWithName.length; j<jLen; j++) {
			var opt = new Option(labelArrWithName[j][1], labelArrWithName[j][0], false, false);
			$selLabel.append(opt);
		}

		// Legend
		var cvsLegend = document.getElementById("cvs_pca" + objNum + "_legend");
		cvsLegend.width = legendW;
		cvsLegend.height = cvsH;
		cvsLegend.setAttribute("width", legendW);
		cvsLegend.setAttribute("height", cvsH);
		var $divLegend = $("#div_cvs_pca" + objNum + "_legend");
		$divLegend.width(legendW + 4);
		$divLegend.height(cvsH + 3);
		obj.id2OtherGrps =  mg_getAccId2ObjMap();
		obj.initLegend("sel_pca" + objNum + "_grp", "cvs_pca" + objNum + "_legend", labelArr);

		// Popup(shared)
		obj.divIdPopup = "graph_popup";
		obj.spanIdCursorInfo = "span_footer_text";
		obj.mousePopupOffsetX = MOUSE_POPUP_OFFSET_X;
		obj.mousePopupOffsetY = MOUSE_POPUP_OFFSET_Y;
	}
	$("#sel_pca1_x").val(0);
	$("#sel_pca1_y").val(1);
	$("#sel_pca2_x").val(0);
	$("#sel_pca2_y").val(2);

	var $td_pca1 =  $("#td_div_cvs_pca1");
	$td_pca1.width(cvsW).height(cvsH);
	$td_pca1.resizable({
		"handles":"se",
		"minWidth":cvsW,
		"maxWidth":cvsW * 2,
		"minHeight":cvsH,
		"maxHeight":cvsH * 2
	}).on("resize",function() {
		var $div_pca1 = $("#div_cvs_pca1");
		$("#div_cvs_pca2").outerWidth($div_pca1.outerWidth()).outerHeight($div_pca1.outerHeight());
//		if(!($('#pca-slide').hasClass('pcs-slide-off'))) {
		if(pcaSlideOpened()) {
			var pca2Width = getPca2Width();
			$('#pca-slide').css("min-width", pca2Width + "px").css("max-width", pca2Width + "px");
			$('#td_div_cvs_pca2').width($('#td_div_cvs_pca1').width());	// For Firefox
		}
		$("#div_cvs_pca1").css({width:"", height:""});	// "width"  conflicts with left/right. "height" conflicts with top/bottom.
	});
	$td_pca1.trigger("resize");

	graphPcaA.onselect = pcaOnselectFunc;
	graphPcaB.onselect = pcaOnselectFunc;
	$("#pca-operation").on("change", pcaOnselectFunc);
	$("#pca-handle").on("click", pcaOnselectFunc);
	$("#ipt_pca_sync").on("change", pcaOnselectFunc);
	pcaOnselectFunc();
}
function pcaSlideOpened() {
	return !($('#pca-slide').hasClass('pcs-slide-off'));
}
function getPca2Width() {
	var $tablePca2 = $("#table_pca2");
	var pca2Width = parseInt($tablePca2.outerWidth()) + parseInt($tablePca2.css("margin-left")) + parseInt($tablePca2.css("left")) + parseInt($("#pca-handle").width()) + 1;
	return pca2Width;
}

var flgFromAcclist = false;
var pcaOnselectFunc = function() {
	// For sync selections.
	var parentCvsId = $(this.cvs).attr("id");
	if(!parentCvsId || !this.cvs) parentCvsId = "cvs_pca1";
//	if( $("#ipt_pca_sync").prop("checked") && !($('#pca-slide').hasClass('pcs-slide-off')) ) {
	if( $("#ipt_pca_sync").prop("checked") && pcaSlideOpened()  ) {
		if(parentCvsId === "cvs_pca1") {
			graphPcaB.setSelectedIds(graphPcaA.selectedIds);
			graphPcaB.draw();
		} else {
			graphPcaA.setSelectedIds(graphPcaB.selectedIds);
			graphPcaA.draw();
		}
	}

	var opMethod = $("#pca-operation").val();
	var destIds = tasukeGraphManager.getMapClone(graphPcaA.getSelectedIds());
//	if($('#pca-slide').hasClass('pcs-slide-off')) {
	if( !(pcaSlideOpened()) ) {
		// Nothing to do.
		if(DEBUG_MODE) console.log("PCA slide off");
	} else {
		var bIdsHash = tasukeGraphManager.getMapClone(graphPcaB.getSelectedIds());
		var bIdsArr = Object.keys(bIdsHash);
		if(opMethod === "or" || opMethod === "sub" || opMethod === "xor") {
			for(var i=0,len=bIdsArr.length; i<len; i++) {
				if(opMethod === "or") {
					 destIds[bIdsArr[i]] = true;
				} else if(opMethod === "sub") {
					if(bIdsArr[i] in destIds) delete destIds[bIdsArr[i]];
				} else { // xor.
					if(bIdsArr[i] in destIds) {
						delete destIds[bIdsArr[i]];
					} else {
						destIds[bIdsArr[i]] = true;
					}
				}
			}
		} else {	// and.
			var andHash = {};
			for(var i=0,len=bIdsArr.length; i<len; i++) {
				if(bIdsArr[i] in destIds) andHash[bIdsArr[i]] = true;
			}
			destIds = andHash;
		}
	}
	selectedAccReg["accs"] = destIds;
	addHistory();

	var destIdsArr = getOrderedIdList(Object.keys(destIds));
/*
	var destIdsArr = Object.keys(destIds);
	if(!idWeight) {
		idWeight = {};
		var accOrder = mg_getAccListObjArray("_Lists-origin");
		for(var i=0,len=accOrder.length; i<len; i++) {
			idWeight[accOrder[i]["id"]] = i;
		}
	}
	destIdsArr.sort(function(a,b) {
		return idWeight[a] - idWeight[b];
	});
*/
	// Preview selected accs.
	var id2obj = mg_getAccId2ObjMap();
	var accInfoArr = [];
	for(var i=0,len=destIdsArr.length; i<len; i++) {
		var obj = id2obj[destIdsArr[i]];
		accInfoArr.push(obj);
	}
	var totalData = graphPcaA.data.length;
	var selDataA =  Object.keys(graphPcaA.getSelectedIds()).length;
	var selDataB =  Object.keys(graphPcaB.getSelectedIds()).length;
	$("#spn_pca1_totalcnt").html(totalData);
	$("#spn_pca1_selcnt").html(selDataA);
	$("#spn_pca2_totalcnt").html(totalData);
	$("#spn_pca2_selcnt").html(selDataB);

	updateSelectedIdsForm();
	if(!flgFromAcclist && $("#div_select_idlist").is(":visible")) syncMg2AccListTabulator();
	flgFromAcclist = false;
}

var idWeight = null;
function getOrderedIdList(destIds) {
	var arr = destIds.concat();	// copy.
	if(!idWeight) {
		idWeight = {};
		var accOrder = mg_getAccListObjArray("_Lists-origin");
		for(var i=0,len=accOrder.length; i<len; i++) {
			idWeight[accOrder[i]["id"]] = i;
		}
	}
	arr.sort(function(a,b) {
		return idWeight[a] - idWeight[b];
	});
	return arr;
}

function updateSelectedIdsForm() { 
	accInfoArr = getSelectionAccInfoArr();
	if(DEBUG_MODE) console.log("Call updateSelectedIdsForm()");
	// Preview selected accs.
	var $tblIdlistTbody = $("#tbl_idlist tbody");
	$tblIdlistTbody.children().remove().end();
	for(var i=0,len=accInfoArr.length; i<len; i++) {
		if(!accInfoArr[i]) continue;	// Placeholder?
		var $tr = $('<tr></tr>');
		$tr.append($('<td></td>').text(i+1).css({"white-space":"nowrap", "padding-left":"10px"}));
		$tr.append($('<td></td>').text(accInfoArr[i]["id"]).css({"white-space":"nowrap"}));
		$tr.append($('<td></td>').text(accInfoArr[i]["name"]).css("white-space", "nowrap"));
		$tr.append($('<td></td>').text(accInfoArr[i]["variety"]).css("white-space", "nowrap"));
		$tr.append($('<td></td>').text(accInfoArr[i]["sub_variety"]).css("white-space", "nowrap"));
		if(i%2) {
			$tr.css("background-color", "white");
		} else {
			$tr.css("background-color", "#e4ebf7");	// e9f2fc
		}
		$tblIdlistTbody.append($tr);
	}

	// Update forms.
	var numDestIdsArr = accInfoArr.length;
	$("#span_idlist_cnt").html(numDestIdsArr);
	if(numDestIdsArr > 0) {
		$("#div_selecting_acc").css("background-color", mgStyle["selectedInfoBg"]);
	} else {
		$("#div_selecting_acc").css("background-color", "transparent");
	}
	if(selectedAccReg["chr"]) {
		var regLen = ((selectedAccReg["enPos"] - selectedAccReg["stPos"] + 1) / 1000000).toFixed(2);
		var regLenHtml = "(" + regLen + "M)";
		if(regLen > 5) regLenHtml = "<span style='color:red;'>" + regLenHtml + "</span>";
		$("#span_idlist_reg").html(selectedAccReg["chr"] + ":" + selectedAccReg["stPos"].toLocaleString() + "-" + 
			selectedAccReg["enPos"].toLocaleString() + " " + regLenHtml);

		 $("#div_selecting_reg").css("background-color", mgStyle["selectedInfoBg"]);		
	} else {
		$("#span_idlist_reg").html("----");

		$("#div_selecting_reg").css("background-color", "transparent");
	}
	var $btnSubmit = $("#btn_idlist_submit");
	if(numDestIdsArr !== 0 || "chr" in selectedAccReg) {
		$btnSubmit.attr("disabled", false);
		$btnSubmit.css("color", "blue");
	} else {
		$btnSubmit.attr("disabled", true);
		$btnSubmit.css("color", "gray");
	}

	// Undo/Redu
	var viewMode = $("#sel_viewmode").val();
	var $btnUndo = $('#btn_undo_whole');
	if(undoBuff.length <= 1 || viewMode === "pca") {
		$btnUndo.prop("disabled", true);
	} else {
		$btnUndo.prop("disabled", false);
	}
	var $btnRedo = $('#btn_redo_whole');
	if(redoBuff.length < 1 ||  viewMode === "pca") {
		$btnRedo.prop("disabled", true);
	} else {
		$btnRedo.prop("disabled", false);
	}

	var $btnAcclistApply = $("#btn_idlist_apply");
	if(numDestIdsArr > 0) {
		$btnAcclistApply.prop("disabled", false);
//		$btnAcclistApply.css("opacity", 1);
	} else {
		$btnAcclistApply.prop("disabled", true);
//		$btnAcclistApply.css("opacity", 0.5);
	}

	setMenu();

	// If you write this here, it will create a circular reference (infinite loop).
//	if($("#div_select_idlist").is(":visible")) syncMg2AccListTabulator();
};
function clearAllSelection() {
	selectedAccReg = {};
	updateSelectedIdsForm();
	if($("#div_select_idlist").is(":visible")) syncMg2AccListTabulator();
}
function getSelectionAccInfoArr() {
	var accInfoArr = [];
	if("accs" in selectedAccReg) {
		var destIdsArr = Object.keys(selectedAccReg["accs"]);
		var accOrder = mg_getAccListObjArray("_Lists-origin");
		var idWeight = {};
		for(var i=0,len=accOrder.length; i<len; i++) {
			idWeight[accOrder[i]["id"]] = i;
		}
		destIdsArr.sort(function(a,b) {
			return idWeight[a] - idWeight[b];
		});
		var id2obj = mg_getAccId2ObjMap();
		for(var i=0,len=destIdsArr.length; i<len; i++) {
			if(!destIdsArr[i] || !(destIdsArr[i] in id2obj)) continue;
			var obj = id2obj[destIdsArr[i]];
			accInfoArr.push(obj);
		}
	}
	return accInfoArr;
}
function openPca() {
	// Init PCA Grach canvas.

	if($("#ipt_idlist_checkbox").prop("checked")) {
		graphPcaA.setSelectedIds(selectedAccReg["accs"]);
	} else {
		graphPcaA.clearSelectedIds();
		selectedAccReg = {};
	}
	graphPcaB.clearSelectedIds();

	//TODO Make it a common function.
	$("#pca-operation").val("or");
	$("#ipt_pca_sync").prop("checked", false);
	$('#pca-slide').addClass('pcs-slide-off');
	$('#pca-slide').css("min-width", "20px").css("max-width", "20px");
	$('#pca-handle-inner').html("<i class=\"fa fa-chevron-circle-right\" aria-hidden=\"true\"></i>");
	$("#pca-handle").attr("title", "Open extended PCA-graph.");
	$("#div_sup_extgraph").css("display", "block");

	var objs = [[graphPcaA, "1"], [graphPcaB, "2"]];
	for(var i=0,len=objs.length; i<len; i++) {
		var obj = objs[i][0];
		var objNum =  objs[i][1];

		$("#sel_zoom_pca" + objNum).val(0);
		obj.changeCvsSizeDefault()
//		obj.clearSelectedIds();
		var divCvsPca = $(obj.cvs).parent("div");
		divCvsPca.css("width", (obj.cvs.width + 4) + "px");
		divCvsPca.css("height", (obj.cvs.height + 4) + "px");
		drawPca(obj, parseInt($("#sel_pca" + objNum + "_x").val()),  parseInt($("#sel_pca" + objNum + "_y").val()));
		obj.drawLegend();
	}
	pcaOnselectFunc();

	// #div_cvs_pca1 was invisible&position:absolute, so fired the resize event to enable top/bottom/left/right style.
	$("#td_div_cvs_pca1").trigger("resize");
}
function drawPca(targetGp, pcx, pcy) {
	targetGp.refresh();
	var db2accInfo = mg_getAccDb2ObjMap();
	var data = [];
	var dbs = storage.getItem("_Lists").split(",");
//	var dbs = Object.keys(pcaInfoOri["data"]);
	for(var i=0,iLen=dbs.length; i<iLen; i++) {
		if( !(dbs[i] in  pcaInfoOri["data"]) ) continue;
		var dbInfo = pcaInfoOri["data"][dbs[i]];
		if( !(dbs[i] in db2accInfo) ) continue;
		data.push([dbInfo["vec_arr"][pcx], dbInfo["vec_arr"][pcy], db2accInfo[dbs[i]]["id"], dbInfo["fid"]]);
	}
	targetGp.xAxis["label"] = "PC" + (pcx+1);
	targetGp.yAxis["label"] = "PC" + (pcy+1);
	targetGp.setData(data);
	targetGp.adjustScaleAuto();
	targetGp.draw();
}
function changePC(targetGp, pcxSelId, pcySelId) {
	targetGp.clearAxis();
	drawPca(targetGp,  parseInt($("#" + pcxSelId).val()),  parseInt($("#" + pcySelId).val()));
}
function changePcaText(selForm, targetGp) {
	targetGp.drawPointText = $(selForm).val();
	targetGp.draw();
}
/*
function changePcaGrp(targetGp) {
	targetGp.changeGrp();
	targetGp.drawLegend();
}
*/
function drawAdmixture(cvs=document.getElementById("cvs_view")) {
	if(!admixtureInfoOri) return;

	var data = admixtureInfoOri["data"];
	var stat = admixtureInfoOri["stat"];

	initCvsViewCache();
//	var chrInfos = getTargetChrInfos();
	var dbs = storage.getItem("_Lists").split(",");

	var ks =  $("#sel_chr").val();	// Always array.
	var kmerW = KMERW;
	scaleAdmixture.setData(ks, kmerW);
	var blockW = BLOCKW;
	var blockH = parseInt($("#sel_blockh").val());
//	var cvs = document.getElementById("cvs_view");
	var ctx = cvs.getContext("2d");
	var canvasW = scaleAdmixture.canvasW;
//	var canvasW = kmerW * ks.length;
	var canvasH = dbs.length * blockH;	
	cvs.setAttribute("width",  canvasW);
	cvs.style.width = canvasW + 'px';
	cvs.setAttribute("height", canvasH);
	cvs.style.height = canvasH + 'px';
	ctx.clearRect(0, 0, cvs.width, cvs.height);

	var lenColors = ADMIXTURE_COLORS.length;
	for(var i=0,iLen=dbs.length; i<iLen; i++) {
		var db = dbs[i];
		if(db in data) {
			var y = blockH * i;
			for(var j=0,jLen=ks.length; j<jLen; j++) {
				var kmer = ks[j];
				var arr = data[db][kmer];
				var stX =  kmerW * j;
				var enX = kmerW * (j+1) - 1;
				var sumVal = 0;
				var x = stX;
				for(var k=0,kLen=arr.length; k<kLen; k++) {
					ctx.fillStyle = ADMIXTURE_COLORS[(k+1) % lenColors - 1];
					ctx.fillRect(x, y, enX - x + 1, blockH);
					var clusterVal = data[db][kmer][k];
					if(clusterVal <= 0.00001) clusterVal = 0;
					sumVal += clusterVal;
//					sumVal +=  data[db][kmer][k];
					x = stX + Math.round(sumVal * kmerW);
				}
			}
		} else {
			//TODO draw blank color;
		}

/*
		ctx.fillStyle = "black";
		for(var j=0,jLen=stat["kmers"].length-1; j<jLen; j++) {
			var enX = kmerW * (j+1) - 1;
			ctx.fillRect(enX, 0, 1, canvasH);
		}
*/
	}

	// Draw ks divide line.
	scaleAdmixture.drawKDivLine(cvs);

	adjustViewScrollbar();
	drawKmerScale();
	clearHeatmapScale();
	dispHeatmapScale(false);
	adjustViewScrollbar();
	if(canvasW>0) {
		bufViewImageOri = ctx.getImageData(0, 0, canvasW, canvasH);
		bufViewImage = bufViewImageOri;
	}
	drawViewSelected();
}
function drawAdmixtureChangeZoom() {
	if(!admixtureInfoOri) return;

	mg_showLoadingDialog();
	setTimeout(function() {
		var stat = admixtureInfoOri["stat"];
		var ks =  $("#sel_chr").val();
		var zoomTo = parseInt($("#sel_zoom").val());
		var cvsW = DEF_VIEWCVS_WIDTH;
		if(ks.length > 1) cvsW = DEF_VIEWCVS_WIDTH_M;
		if(zoomTo > 0) {
			var unitW = cvsW / 4;
			cvsW = cvsW + unitW * zoomTo;
		} else if(zoomTo < 0){
			cvsW = cvsW  * 3 / (4 + (zoomTo + 1) * -1);
		}
		var kmerW;
		if(ks.length === 0) { 
			kmerW = 0;
		} else {
			kmerW = parseInt(cvsW / ks.length);
		}
		if(kmerW < 1) kmerW = 1;
		KMERW = kmerW;
//		storage.setItem("mg_kmerW", kmerW);
		drawAdmixture();
		mg_closeLoadingDialog();
	}, 10);
}
function downloadCanvasImage(cvs, fname, type='image/png') {
	var img = new Image();
	var link = document.createElement('a');
	link.href = cvs.toDataURL(type);
	link.download = fname;
	link.click();
}
function downloadData(data, fname, type='text/plain') {
	var blob = new Blob([data], { type: type});
	var url = URL.createObjectURL(blob);
	var a = document.createElement('a');
	a.href = url;
	a.download = fname;
	a.click();
	URL.revokeObjectURL(url);
}
function exportGraphImage() {
	var viewMode = $("#sel_viewmode").val();
	if(viewMode === "pca") {
		exportPcaImage();
	} else {
		exportVariantCountImage();
	}
}
function exportVariantCountImage() {
	var imgTree = bufTreeImage;
	var imgTitle = bufTitleImage;
	var imgView = bufViewImage;
	var imgScale = bufScaleImage;

	var mode = $("#sel_viewmode").val();
	var cvsTree = document.getElementById("cvs_tree");
	var cvsView = document.getElementById("cvs_view");
	var cvsScale = document.getElementById("cvs_scale");
	var cvsLegend = document.getElementById("cvs_hm_scale");

	// Draw image.
	var titleW = parseInt(storage.getItem("_TitleW"));
	var viewW = cvsView.width;
	var treeW = 0;
	if(imgTree) treeW = cvsTree.width;
//	if(imgTree) treeW = parseInt(storage.getItem("_SystemTreeW"));
	var legendW = 0;
	if(mode === "varcount" || mode === "snpeff") legendW = cvsLegend.width;
	var cvsW = treeW + titleW + viewW + legendW;
	var cvsH = parseInt(cvsView.height) + parseInt(cvsScale.height);
	var cvs = document.createElement("canvas");
	cvs.width = cvsW;
	cvs.height = cvsH;
	cvs.setAttribute("width", cvsW);
	cvs.setAttribute("height", cvsH);
	var ctx = cvs.getContext("2d");
	ctx.putImageData(imgScale, treeW+titleW, 0);
	var x = 0;
	if(imgTree) {
		ctx.putImageData(imgTree, 0, scaleGenomes.canvasH);
		x += treeW;
	}
	ctx.putImageData(imgTitle, x, scaleGenomes.canvasH);
	x += titleW;
	ctx.putImageData(imgView, x, scaleGenomes.canvasH);
	x += viewW; 
	if(mode === "varcount" || mode === "snpeff")  {
		var imgLegend = cvsLegend.getContext("2d").getImageData(0, 0, cvsLegend.width, cvsLegend.height);
		ctx.putImageData(imgLegend, x, 0);
	}

	// Image output
	var viewMode = $("#sel_viewmode").val();
	var fname = "tasuke_variantcount.png";
	if(viewMode === "snpeff") {
		 fname = "tasuke_variantcount_high.png";
	} else if(viewMode === "admixture") {
		 fname = "tasuke_admixture.png";
	}
	downloadCanvasImage(cvs, fname, "image/png");

}
function exportPcaImage() {
	var graphMargin = 30;
	var cvsA = graphPcaA.exportAsCanvas($("#sel_pca1_x option:selected").text(),  $("#sel_pca1_y option:selected").text());
	var w = cvsA.width;
	var h = cvsA.height;
	var cvsB = null;
	if(pcaSlideOpened()) {
		cvsB = graphPcaB.exportAsCanvas($("#sel_pca2_x option:selected").text(),  $("#sel_pca2_y option:selected").text());
		w += graphMargin + cvsB.width;
		if(h < cvsB.height) h = cvsB.height;
	}
	var cvsExp = document.createElement("canvas");
	cvsExp.width = w;
	cvsExp.height = h;
	cvsExp.setAttribute("width", w);
	cvsExp.setAttribute("height", h);
	var ctxExp = cvsExp.getContext("2d");
	ctxExp.putImageData(cvsA.getContext("2d").getImageData(0,0,cvsA.width,cvsA.height), 0, 0);
	if(cvsB) ctxExp.putImageData(cvsB.getContext("2d").getImageData(0,0,cvsB.width,cvsB.height), graphMargin+cvsA.width, 0);

	downloadCanvasImage(cvsExp, "tasuke_pca.png", "image/png");
}
function exportVariantCountFile(mode="dens_arr") {
	// mode=dens_arr, eff_arr

	var en = JSON.parse(storage.getItem("_Env"));
	var res = "#BlockSize(bp)=" + BLOCKSIZE + "\n";

	var lists = mg_getAccListObjArray("_Lists");
	var resObj = recountVaraint(mode);
	var chrInfos = null;
	if($("#sel_viewmode").val() === "varcount" || $("#sel_viewmode").val() === "snpeff") {
		// Selected chrs only.
		chrInfos = getTargetChrInfos();
	} else {
		// All chrs.
		chrInfos = JSON.parse(storage.getItem("_Chrs"));
	}
	var chrInfoMap = {};
	for(var i=0,len=chrInfos.length; i<len; i++) {
		var chr = chrInfos[i]["id"];
		chrInfoMap[chr] = true;
		if(chr in resObj["stat"]["stat"]) res += "#Blocks:" + chr + "=" + resObj["stat"]["stat"][chr] + "\n";
	}
	
	for(var i=0,iLen=lists.length; i<iLen; i++) {
		var db = lists[i]["db"];
		for(var j=0,jLen=resObj["data"][db].length; j<jLen; j++) {
			var chr = resObj["data"][db][j]["chr"];
			if( !(chr in chrInfoMap) ) continue;

			res += lists[i]["id"] + "\t" + en.chromosome_name + chr + "\t" + resObj["data"][db][j]["dens_arr"].join("\t") + "\n";
		}
	}
	var fname = "tasuke_varcount.txt";
	if(mode === "eff_arr") fname = "tasuke_varcount_high.txt";
	downloadData(res, fname);
}
function exportPcaFiles() {
	var lists = mg_getAccListObjArray("_Lists");
	var db2info = {};
	for(var i=0,iLen=lists.length; i<iLen; i++) {
		db2info[lists[i]["db"]] = lists[i];
	}
//	var db2info = mg_getAccDb2ObjMap();
//	var dbOrder = pcaInfoOri["stat"]["dbOrder"];
	var pcs = parseInt(pcaInfoOri["stat"]["pcs"]);
	var res = "";
	res += "#FID\tIID";
	for(var i=1; i<=pcs; i++) {
		res += "\tPC" + i;
	}
	res += "\n";
	for(var i=0,iLen=lists.length; i<iLen; i++) {
		var db = lists[i]["db"];
		var vecArr = [];
		var fid = "";
		if(db in pcaInfoOri["data"]) {
			var accData = pcaInfoOri["data"][db];
			fid =  accData["fid"];
			for(var j=0,jLen=accData["vec_arr"].length; j<jLen; j++) {
				vecArr.push(tasukeGraphAxis.getRoundNumber(accData["vec_arr"][j], 6));
			}
		} else {
			vecArr = new Array(pcs).fill("na");
		}
		res += fid + "\t" + lists[i]["id"] + "\t" + vecArr.join("\t") + "\n";
	}

	if(pcaInfoOri["stat"]["val"]) {
		var zip = new JSZip();
		var folder = zip.folder("tasuke_pca_files");
		folder.file("tasuke_pca_vec.txt", res);

		var resVal = pcaInfoOri["stat"]["val"].join("\n") + "\n";
		folder.file("tasuke_pca_val.txt", resVal);
		zip.generateAsync({ type: "blob" }).then(function(blob) {
			downloadData(blob, "tasuke_pca_files.zip");
		});
	} else {
		downloadData(res, "tasuke_pca_vec.txt");
	}
}
function exportAdmixtureFiles() {
	 if(!admixtureInfoOri) return;

	var lists = mg_getAccListObjArray("_Lists");
	var kmers;
	if($("#sel_viewmode").val() === "admixture") {
		// Selected ancesters only.
		kmers = $("#sel_chr").val();
	} else {
		// All ancesters.
		kmers = admixtureInfoOri["stat"]["kmers"];
	}
	var zip = new JSZip();
	var folder = zip.folder("tasuke_admixture_files");
	var resIds = "";
	var doneResIds = false;
	for(var i=0,iLen=kmers.length; i<iLen; i++) {
		var kmer = kmers[i];
		var resKmer = "";
		for(var j=0,jLen=lists.length; j<jLen; j++) {
			var db = lists[j]["db"];
			vals = [];
			if(db in admixtureInfoOri["data"]) {
				var valsOri = admixtureInfoOri["data"][db][kmer];
				for(var k=0,kLen=valsOri.length; k<kLen; k++) {
					vals.push( valsOri[k].toFixed(6).padEnd(6,"0"));
				}
			} else {
				vals = new Array(kmer).fill("na");
			}
//console.log(db + ", " + kmer + ", " + vals.join("\t"));
			resKmer += vals.join(" ") + "\n";

			if(!doneResIds) resIds += lists[j]["id"] + "\n";
		}
		folder.file("tasuke_admixfure." + kmer + ".txt", resKmer);

		if(!doneResIds)  folder.file("tasuke_admixfure.ids.txt", resIds);
		resIds = "";
		doneResIds = true;
	}

	zip.generateAsync({ type: "blob" }).then(function(blob) {
		 downloadData(blob, "tasuke_admixture_files.zip");
	});
}
function drawVariantDencity(cvs=document.getElementById("cvs_view")) {
	// Notice: I tried implementing a progress bar using setInterval() in this function,
	// but it became unusually slow, so I discontinued it.
	// Rather, it is getAndDrawVaraintDencity() that takes time.

	if(!variantDensInfo) return;
	if(DEBUG_MODE) console.log("drawVariantDencity: start");

	initCvsViewCache();
	var chrInfos = getTargetChrInfos();
	var densAll = variantDensInfo["data"];
	var chrStat = variantDensInfo["stat"];
	var dbs = storage.getItem("_Lists").split(",");

	scaleGenomes.setData(chrInfos, chrStat["stat"], BLOCKW, BLOCKSIZE);
	var blockW = BLOCKW;
	var blockH = parseInt($("#sel_blockh").val());
//	var cvs = document.getElementById("cvs_view");
	var ctx = cvs.getContext("2d");
	var canvasW = scaleGenomes.canvasW;
	var canvasH = dbs.length * blockH;
	cvs.setAttribute("width",  canvasW);
	cvs.style.width = canvasW + 'px';
	cvs.setAttribute("height", canvasH);
	cvs.style.height = canvasH + 'px';

	for(var i=0,iLen=dbs.length; i<iLen; i++) {
		var densAcc = densAll[dbs[i]];
		var chr2dens = {};
		for(var j=0,jLen=densAcc.length; j<jLen; j++) {
			chr2dens[densAcc[j]["chr"]] = densAcc[j];
		}
		var block = 0;
		for(var j=0,jLen=chrInfos.length; j<jLen; j++) {
			densAccChr = chr2dens[chrInfos[j].id];
			var densArr = densAccChr["dens_arr"];
			for(var k=0,kLen=densArr.length; k<kLen; k++) {
				var dens = densArr[k];
				ctx.fillStyle = getDencityColor(chrStat, dens);
				ctx.fillRect(block * blockW, i*blockH, blockW, blockH);
				block++;
			}
		}
	}

	// Draw chr divide line.
	scaleGenomes.drawChrDivLine(cvs);

	adjustViewScrollbar();
	drawGenomeScale();
	drawHeatmapScale(variantDensInfo["stat"]);
	dispHeatmapScale(true);
	adjustViewScrollbar();	//TODO It takes two.

	if(canvasW > 0) {
		bufViewImageOri = ctx.getImageData(0, 0, canvasW, canvasH);
		bufViewImage = bufViewImageOri;
	}
	drawViewSelected();
	if(DEBUG_MODE) console.log("drawVariantDencity: end");
}
function dispHeatmapScale(disp=true) {
	if(disp) {
		$("#main_view_div").css("right", $("#cvs_hm_scale").width() + "px");
	} else {
		$("#main_view_div").css("right", "0px");
	}
}
function drawVariantDencityAutoFit() {
	if(!variantDensInfoOri) return;

	var oriBlockSize = variantDensInfoOri["stat"]["blockSize"];
	var wm = getDefaultBlock();
	var blockWidth = wm[0];
	var blockSize = oriBlockSize * wm[1];
	BLOCKW =  wm[0];
	BLOCKSIZE = blockSize;
//	storage.setItem("mg_blockW", wm[0]);
//	storage.setItem("mg_zoom", blockSize);

	var mode = $("#sel_viewmode").val();
	var arrkey = "dens_arr";
	if(mode === "snpeff") arrkey = "eff_arr";
	variantDensInfo = recountVaraint(arrkey);
	drawVariantDencity();
}
function drawVariantDencityChangeZoom(callback=null) {
	if(!variantDensInfoOri) return;

	mg_showLoadingDialog();
	setTimeout(function() {
		var zoomTo = parseInt($("#sel_zoom").val());
		var wm = getDefaultBlock();
		var blockW = wm[0];
		var blockMulti = wm[1];
		if(zoomTo > 0) {
			var destBlockMulti = blockMulti - zoomTo;
			if(destBlockMulti > 0) {
				blockMulti = destBlockMulti;
			} else {
				blockMulti = 1;
				blockW -= (destBlockMulti - 1);	// Always positive.
			}
		} else if(zoomTo < 0) {
			var destBlockW =  blockW + zoomTo;
			if(destBlockW > 0) {
				blockW = destBlockW;
			} else {
				blockW = 1;
				blockMulti -= (destBlockW - 1);	// Always positive.
			}
		} else {
			// Nothing to do.
		}
		var oriBlockSize = variantDensInfoOri["stat"]["blockSize"];
		var blockSize = oriBlockSize * blockMulti;

		var srcBlockW = BLOCKW;	
		var srcBlockSize = BLOCKSIZE;
		if(blockW !== srcBlockW || blockSize !== srcBlockSize) {
			BLOCKW = blockW;
			BLOCKSIZE = blockSize;
			console.log("blockWidth:" + blockW + ", blockSize:*" +  blockMulti);
			var extText = "";
			if(blockW===1 && blockSize===variantDensInfoOri["stat"]["blockSize"]) extText = "[std]";
			$("#span_footer_blockinfo").html(blockSize.toLocaleString() + "bp/" + blockW + "px" + extText + " :");
			
			var mode = $("#sel_viewmode").val();
			var arrkey = "dens_arr";
			if(mode === "snpeff") arrkey = "eff_arr";
			variantDensInfo = recountVaraint(arrkey);
			drawVariantDencity();
		}
		if(callback) callback();
		mg_closeLoadingDialog();
	}, 10);
}
function drawViewSelected() {
	initCanvasImage();
	if( !("accs" in selectedAccReg) && !("stPos" in selectedAccReg) ) return;

	var useRegionMask = false;
	var stX, enX;
	if("stPos" in selectedAccReg) {
		var viewMode = $("#sel_viewmode").val();
		if(viewMode === "varcount" || viewMode === "snpeff") {
			stX = this.scaleGenomes.getX(selectedAccReg["chr"], selectedAccReg["stPos"]);
			enX = this.scaleGenomes.getX(selectedAccReg["chr"], selectedAccReg["enPos"]);
/*
			var cvs =  document.getElementById("cvs_view");
			var ctx = cvs.getContext("2d");
			ctx.save();
			ctx.fillStyle = mgStyle["regionMask"];
			ctx.globalCompositeOperation = 'source-over';
			if(stX) {
				ctx.fillRect(0, 0, stX-1, cvs.height);
				ctx.fillRect(enX+1, 0, cvs.width-enX, cvs.height);
			} else {
				// It is showing a different Chr than the selection.
				ctx.fillRect(0, 0, cvs.width, cvs.height);
			}
			ctx.restore();
			bufViewImage = ctx.getImageData(0, 0, cvs.width, cvs.height);
*/
			var cvsScale =  document.getElementById("cvs_scale"); 
			var ctxScale = cvsScale.getContext("2d");
			ctxScale.fillStyle = _setColor().chrareacolor;
			ctxScale.fillRect(stX, 0, enX-stX+1, cvsScale.height);
			if(cvsScale.width > 0) bufScaleImage = ctxScale.getImageData(0, 0, cvsScale.width, cvsScale.height);

			useRegionMask = true;
		}
	}

	var drawRegMaskFunc = function(cvs, ctx, selectedAcc, y) {
		ctx.save();
		ctx.fillStyle = mgStyle["regionMask"];
		if(stX && selectedAcc) {
			ctx.fillRect(0, y, stX-1, blockH);
			ctx.fillRect(enX+1, y, cvs.width-enX, blockH);
		} else {
			// It is showing a different Chr than the selection.
			ctx.fillRect(0, y, cvs.width, blockH);
		}
		ctx.restore();
	};

	var lenTargetAccs = 0;
	if("accs" in selectedAccReg) lenTargetAccs = Object.keys(selectedAccReg["accs"]).length;
	if(("accs" in selectedAccReg && lenTargetAccs>0) || useRegionMask) {
		var dbs = storage.getItem("_Lists").split(",");
		var db2accInfo = mg_getAccDb2ObjMap();
		var blockH = parseInt($("#sel_blockh").val());
		var targetCvsIds = ["cvs_tree", "cvs_list", "cvs_view"];
		for(var i=0,iLen=targetCvsIds.length; i<iLen; i++) {
			var cvs = document.getElementById(targetCvsIds[i]);
			if(cvs.width === 0) continue;	// No Chr/K is specified.
			var ctx = cvs.getContext("2d");
			ctx.save();
			for(var j=0,jLen=dbs.length; j<jLen; j++) {
				ctx.globalCompositeOperation = 'source-over';
				var selectedAcc = false;
				if("accs" in selectedAccReg && db2accInfo[dbs[j]]["id"] in selectedAccReg["accs"]) selectedAcc = true;

				var y = blockH * j;
				if(targetCvsIds[i] === "cvs_view" && useRegionMask) {
					var rowMask = selectedAcc;
					if(lenTargetAccs === 0) rowMask = true;	// If no Acc is selected, all Accs will be targeted in the end.
					drawRegMaskFunc(cvs, ctx, rowMask, y);
					ctx.globalCompositeOperation = 'lighter';
				}
				if(selectedAcc) {
					ctx.fillStyle = mgStyle["selectedRow"];
					ctx.fillRect(0, y, cvs.width, blockH);
				}
			}
			if(targetCvsIds[i] === "cvs_tree") {
				bufTreeImage = ctx.getImageData(0, 0, cvs.width, cvs.height);
			} else if(targetCvsIds[i] === "cvs_list") {
				bufTitleImage = ctx.getImageData(0, 0, cvs.width, cvs.height);
			} else {
				if(useRegionMask) {
					ctx.globalCompositeOperation = 'source-over';
					ctx.strokeStyle = mgStyle["regionMaskFrame"];
					ctx.strokeRect(stX-1, 0, enX-stX+3, cvs.height);
				}
				bufViewImage = ctx.getImageData(0, 0, cvs.width, cvs.height);
			}
			ctx.restore();
		}
	}

/*
	if("accs" in selectedAccReg && Object.keys(selectedAccReg["accs"]).length) {
		var dbs = storage.getItem("_Lists").split(",");
		var db2accInfo = mg_getAccDb2ObjMap();
		var targetIdx = [];
		for(var i=0,len=dbs.length; i<len; i++) {
			if(db2accInfo[dbs[i]]["id"] in selectedAccReg["accs"]) {
				targetIdx.push(i);
			}
		}
		var targetCvsIds = ["cvs_tree", "cvs_list", "cvs_view"];
		var blockH = parseInt($("#sel_blockh").val());
		for(var i=0,iLen=targetCvsIds.length; i<iLen; i++) {
			var cvs = document.getElementById(targetCvsIds[i]);
			if(cvs.width === 0) continue;	// No Chr/K is specified.
			var ctx = cvs.getContext("2d");
			ctx.save();
			ctx.fillStyle = mgStyle["selectedRow"];
			if(targetCvsIds[i] === "cvs_view" && useRegionMask) ctx.globalCompositeOperation = 'lighter';
			for(var j=0,jLen=targetIdx.length; j<jLen; j++) {
				var y = blockH * targetIdx[j];
				ctx.fillRect(0, y, cvs.width, blockH);
			}
			ctx.restore();
			if(targetCvsIds[i] === "cvs_tree") {
				bufTreeImage = ctx.getImageData(0, 0, cvs.width, cvs.height);
			} else if(targetCvsIds[i] === "cvs_list") {
				bufTitleImage = ctx.getImageData(0, 0, cvs.width, cvs.height);
			} else {
				bufViewImage = ctx.getImageData(0, 0, cvs.width, cvs.height);
			}
		}
	}
*/
}
function changeViewMode() {
	syncMode2Form();
	var  viewMode = $("#sel_viewmode").val();
	$("#span_footer_blockinfo").html("");
	if(viewMode === "pca") {
		openPca();
	} else {
		drawChangeZoomFactory();
	}
}
function changeChr() {
	drawChangeZoomFactory();

	var  viewMode = $("#sel_viewmode").val();
	if(viewMode === "varcount" || viewMode === "snpeff") {
		var chrsStr = JSON.stringify($("#sel_chr").val());
		storage.setItem("_Chr", chrsStr);
	}
}
function drawChangeZoomFactory() {
	if($("#ipt_idlist_checkbox").prop("checked")) {
		// Nothing to do.
	} else {
		selectedAccReg = {};
		addHistory();
	}

//	adjustBlockHForFirefox();

	var viewMode = $("#sel_viewmode").val();
	if(viewMode === "varcount" || viewMode === "snpeff") {
		BLOCKW = DEF_BLOCKW;	//TODO What for?
		drawVariantDencityChangeZoom();
	} else if(viewMode === "pca") {
		// Processing should never come here.
	} else {
		drawAdmixtureChangeZoom();
	}
	updateSelectedIdsForm();
	if($("#div_select_idlist").is(":visible")) syncMg2AccListTabulator();
}
function changePcaZoom(formId, val) {
	$("#" + formId).val(val).trigger("input");
}

var lastViewMode = "";
function syncMode2Form() {
	//TODO use onHide() event?
	var en = JSON.parse(storage.getItem("_Env"));

	var viewMode = $("#sel_viewmode").val();
	var divAccList = $("#div_acclist");
	var divPca =  $("#div_pca");
	if(viewMode === "pca") {
		divAccList.css("display", "none");
		divPca.css("display", "block");
	} else {
		divAccList.css("display","block");
		divPca.css("display", "none");

		var $chrTitle = $("#sel_chr_title");
		var $chrForm = $("#sel_chr");
		var prevVal = $chrForm.val();
		$chrForm.children().remove().end();
//console.log("viewMode:" + viewMode);
		if(viewMode === "admixture") {
			$chrTitle.html("#Ancester(K)");
//			$chrForm.append(new Option("All", "", false, true));
			var kmers = JSON.parse(storage.getItem("mg_kmers"));
			for(var i=0,len=kmers.length; i<len; i++) {
				var opt = new Option("K=" + kmers[i], kmers[i], false, true);
				$chrForm.append(opt);
			}
//			if(storage.getItem("mg_kmer")) $chrForm.val([storage.getItem("mg_kmer")]);
		} else {
			var chrPrefix = "";
			if(en["chromosome_name"]) chrPrefix = en["chromosome_name"];
			$chrTitle.html("CHR");
			var chrs = JSON.parse(storage.getItem("_Chrs"));
			for (var i=0,len=chrs.length; i<len; i++){
				var opt = new Option(chrPrefix + chrs[i].id, chrs[i].id, false, true);
//console.log("Add:" + chrs[i].id);
				$chrForm.append(opt);
			}
			if(storage.getItem("_Chr")) {
				var chrs = [];
				try {
					chrs = JSON.parse(storage.getItem("_Chr"));
				} catch(e) {
					chrs = [storage.getItem("_Chr")];
					storage.setItem("_Chr", JSON.stringify(chrs));
				}
				$chrForm.val(chrs);
			}
//			if(storage.getItem("_Chr")) $chrForm.val([storage.getItem("_Chr")]);
			if(lastViewMode === 'varcount' || lastViewMode === 'snpeff')  $chrForm.val(prevVal);
		}

		$chrForm.multipleSelect('refresh');
	}
	lastViewMode = viewMode;
}

function getDefaultBlock() {
	var targetChrInfos = getTargetChrInfos(); 
	var totalGenomeSize = 0;
	if(targetChrInfos.length > 1) {
		for(var i=0,len=targetChrInfos.length; i<len; i++) {
			totalGenomeSize += parseInt(targetChrInfos[i]["size"]);
		}
	} else {
		var chrInfos = JSON.parse(storage.getItem("_Chrs"));
		var maxChrSize = 0;
		for(var i=0,len=chrInfos.length; i<len; i++) {
			var chrSize = parseInt(chrInfos[i]["size"]);
			if(maxChrSize < chrSize)  maxChrSize = chrSize;
		}
		totalGenomeSize = maxChrSize;
	}
	var idealCanvasWidth = DEF_VIEWCVS_WIDTH;
		if(targetChrInfos.length>1) idealCanvasWidth = DEF_VIEWCVS_WIDTH_M;
	var oriBlockSize = variantDensInfoOri["stat"]["blockSize"];
	var blockSize = oriBlockSize;
	var blockWidth = 1;
	var blockSizeMulti = 1;
	var blocks = Math.ceil(totalGenomeSize / blockSize);
	if(blocks * blockWidth > idealCanvasWidth) {
		while(true) {
			blockSizeMulti++;
			blocks = Math.ceil(totalGenomeSize / (blockSize * blockSizeMulti));
//console.log("Test:" + blocks + "*" + blockSize + "/" + idealCanvasWidth);
			if(blockWidth * blocks < idealCanvasWidth) break;
		}		
	} else {
		while(true) {
			blockWidth++;
//console.log("Test2:" + (blocks * blockWidth) + "/" + idealCanvasWidth);
			if(blockWidth * blocks > idealCanvasWidth) break;
		}
	}
	return [blockWidth,  blockSizeMulti];
}
function drawKmerScale() {
	var en = JSON.parse(storage.getItem("_Env"));	// Currently unused
	scaleAdmixture.draw(en);
	if(scaleAdmixture.cvs.width > 0) {
		bufScaleImageOri = scaleAdmixture.bufImage;
		bufScaleImage = bufScaleImageOri;
	}
}

function drawGenomeScale() {
	// blockW
	// chrnum2chr
	// chrnum2BlockDiv
	var en = JSON.parse(storage.getItem("_Env"));
	scaleGenomes.draw(en);
	if(scaleGenomes.cvs.width > 0) {
		bufScaleImageOri = scaleGenomes.bufImage;
		bufScaleImage = bufScaleImageOri;
	}
}
function clearHeatmapScale() {
	var cvs = document.getElementById("cvs_hm_scale");
	var ctx = cvs.getContext("2d");
	ctx.clearRect(0, 0, cvs.width, cvs.height);
}
function drawHeatmapScale(chrStat) {
        var en = JSON.parse(storage.getItem("_Env"));
	var cvs = document.getElementById("cvs_hm_scale");
	var ctx = cvs.getContext("2d");

	var cvsHm = document.getElementById("cvs_view");
	var h = HEATMAP_SCALE;
//	var h = parseInt(storage.getItem("mg_hmScale_defH"));
	if(parseInt(cvsHm.getAttribute("height")) < h) h = cvsHm.height;
	cvs.height = h;
	cvs.setAttribute("height", h);
	ctx.font="9px 'Helvetica'";
	ctx.textAlign = 'left';

	var h = cvs.height;
	var scaleT = 7;
	var scaleB = scaleT;
	var scaleL = 5;
	var scaleW = 7;
	var scaleH = h - scaleT - scaleB;

	ctx.clearRect(0, 0, cvs.width, cvs.height);
	var drawTriangle = function(ctx, y) {
		ctx.beginPath();
		var l = scaleL + scaleW + 2;
		ctx.moveTo(l, y);
		ctx.lineTo(l+3, y-2);
		ctx.lineTo(l+3, y+2);
		ctx.lineTo(l, y);
		ctx.closePath();
		ctx.fillStyle = "black";
		ctx.fill();
		ctx.stroke();
	}

	var textX = scaleL + scaleW + 7;
	var textW = cvs.width - textX;

	var valueUnit = (chrStat["maxAdjust"] - chrStat["min"]) / scaleH;
	ctx.textBaseline = "middle";
	ctx.fillStyle = "black";
	ctx.strokeStyle = "black";

	// maxAdjust
	var y = scaleT;
	drawTriangle(ctx, y);
	ctx.fillText(">" + chrStat["maxAdjust"], textX, y, textW); 

	// avg
	y = Math.round(scaleT + (chrStat["maxAdjust"] - chrStat["avg"]) / valueUnit);
	drawTriangle(ctx, y);
	ctx.fillText(chrStat["avg"], textX, y, textW);
	ctx.fillText("[avg]", textX, y + 9, textW);

	// min
	y = h - scaleB;
	drawTriangle(ctx, y);
	ctx.fillText(chrStat["min"], textX, y, textW);

	for(var i=0; i<scaleH; i++) {
		var cntVar = chrStat["maxAdjust"] - i * valueUnit;
		ctx.fillStyle = getDencityColor(chrStat, cntVar);
		ctx.fillRect(scaleL, scaleT + i, scaleW,1);
	}

	ctx.strokeStyle = "rgba(0,0,0,0.7)";
	ctx.strokeRect(scaleL, scaleT, scaleW, scaleH);
}
var timeoutDispPopup = null;
function drawCanvasSelection(cvs, XY) {
	var trigCvsId = cvs.getAttribute("id");
	var viewMode = $("#sel_viewmode").val();

	var cnvpos = null;
	var isDragging = false;
	if(XY.x1 !== null) {
		isDragging = true;
		cnvpos = getBlockPos(XY);
	} else {
		var blockW = BLOCKW;
		var chrreg = null;
		if(viewMode !== "admixture") chrreg = scaleGenomes.getChrreg(XY.lastX);
		cnvpos = getBlockPos({"x1":XY.lastX, "y1":XY.lastY, "x2":XY.lastX, "y2":XY.lastY, "lastX":XY.lastX, "lastY":XY.lastY, "chrreg":chrreg});
	}
	var xl = cnvpos[0];
	var xr = cnvpos[1];
	var yt = cnvpos[2];
	var yb = cnvpos[3];
	var w = xr - xl + 1;
	var h = yb - yt + 1;
	var ctx = document.getElementById("cvs_view").getContext("2d");
	prevCanvasImage();

	var strokeStyle = "white";
	 var fillStyle = mgStyle["selectingRow"];
	var gco = "source-over";	// source-over, lighter,xor

	ctx.save();

	var onRegSel = onRegionSelect(viewMode, trigCvsId);

	// cvs_view
	var cvs = document.getElementById("cvs_view");
	var ctx = cvs.getContext("2d");
	ctx.globalCompositeOperation = gco;
	ctx.strokeStyle = strokeStyle;
	ctx.fillStyle = fillStyle;
	if(trigCvsId === "cvs_view") {
		ctx.fillRect(0, yt-1, cvs.width, h+1);	// horizonta
		if(isDragging) {
			if(onRegSel) {
				ctx.fillRect(xl, 0, w, cvs.height);
				ctx.strokeRect(xl, yt, w, h);
			} else {
				ctx.strokeRect(0, yt, cvs.width, h);
			}
		}
	} else if(trigCvsId === "cvs_scale") {
		if(viewMode !== "admixture") {
			ctx.fillRect(xl, 0, w, cvs.height);	// vertical
			if(onRegSel) ctx.strokeRect(xl, 0, w, cvs.height);
		}
	} else {
		ctx.fillRect(0, yt-1, cvs.width, h+1);	// horizontal
	}
	drawCursorLine(trigCvsId, ctx, isDragging, cnvpos);
	var cvsView = cvs;

	// cvs_tree
	cvs = document.getElementById("cvs_tree");
	ctx = cvs.getContext("2d");
	ctx.globalCompositeOperation = gco;
	ctx.strokeStyle = strokeStyle;
	ctx.fillStyle = fillStyle;
	if(trigCvsId !== "cvs_scale") {
		ctx.fillRect(0, yt-1, cvs.width+1, h+1);
		if(isDragging) if(trigCvsId === "cvs_tree") ctx.strokeRect(0, yt, cvs.width, h);
	}
	 drawCursorLine(trigCvsId, ctx, isDragging, cnvpos);

	// cvs_list
	cvs = document.getElementById("cvs_list");
	ctx = cvs.getContext("2d");
	ctx.globalCompositeOperation = gco;
	ctx.strokeStyle = strokeStyle;
	ctx.fillStyle = fillStyle;
	if(trigCvsId !== "cvs_scale") {
		ctx.fillRect(0, yt-1, cvs.width, h+1);
		if(isDragging) if(trigCvsId === "cvs_list") ctx.strokeRect(0, yt, cvs.width, h);
	}
	drawCursorLine(trigCvsId, ctx, isDragging, cnvpos);

	// cvs_scale
	cvs = document.getElementById("cvs_scale");
	ctx = cvs.getContext("2d");
	ctx.globalCompositeOperation = gco;
	ctx.strokeStyle = strokeStyle;
	ctx.fillStyle = _setColor().chrareacolor;
	if(trigCvsId === "cvs_view" || trigCvsId === "cvs_scale") {
		if(viewMode !== "admixture") {
			if(onRegSel) ctx.fillRect(xl, 0, w, cvs.height);
			if(isDragging && trigCvsId === "cvs_scale") ctx.strokeRect(xl, 0, w, cvs.height);

			// Current CHR pos(Mbp).
			ctx.fillStyle = "black";
			ctx.font = "9px 'Helvetica'";
			ctx.textAlign = "left";
			ctx.textBaseline = "top";
			var lastChrPos = cnvpos[7];
			ctx.fillText((lastChrPos / 1000000).toFixed(2) + "M", XY.lastX + 2, 3);
		}
		ctx.fillRect(XY.lastX, 0, 1, cvs.height);	// Cursor line.
	}

	ctx.restore();

	// Display footer-text/popup-dialog.
	var $spanDispReg = $("#span_footer_text")
	var text = "";
	var chr, lastPos;
	if((trigCvsId === "cvs_view" || trigCvsId === "cvs_scale") && (viewMode === "varcount" || viewMode === "snpeff")) {
		chr = cnvpos[4];
		lastPos =  cnvpos[7];
		text = chr + ":" + lastPos.toLocaleString() + " ";
	}
	if(trigCvsId !== "cvs_scale") {
		var targetAccs = getBlockAccs(XY.lastY, XY.lastY);
		text += "[" + targetAccs[0][storage.getItem("_Name")] + "]";

		if(trigCvsId === "cvs_view" && XY.x1 === null && storage.getItem("_popupDetail") == 1) {
			if(timeoutDispPopup) clearTimeout(timeoutDispPopup); 
			var cvsOffset = $(cvsView).offset();

			if(viewMode === "admixture") {
				timeoutDispPopup = setTimeout(function() {
					popupAdmixtureDetail(targetAccs[0]["id"], XY.lastX + cvsOffset.left + MOUSE_POPUP_OFFSET_X, XY.lastY + cvsOffset.top + MOUSE_POPUP_OFFSET_Y);
					timeoutDispPopup = null;
				}, DEF_POPUP_TIMELAG);
			} else {
				 timeoutDispPopup = setTimeout(function() {
					popupVariantCountDetail(targetAccs[0]["id"], XY.lastX + cvsOffset.left + MOUSE_POPUP_OFFSET_X, XY.lastY + cvsOffset.top + MOUSE_POPUP_OFFSET_Y, chr, lastPos); 
					timeoutDispPopup = null;
				}, DEF_POPUP_TIMELAG);
			}
		}
	}
	$spanDispReg.text(text);
/*
	if(isDragging) {
		$spanDispReg.text(chr + ":" + stPos.toLocaleString() + "-" + enPos.toLocaleString());
	} else {
		$spanDispReg.text(chr + ":" + lastPos.toLocaleString());
	}
*/
}
function getVariantCount(accDb, chr, pos) {
	if(!variantDensInfo) return null;

	var chrBlockIndex = Math.floor(pos / BLOCKSIZE);
	return variantDensInfo["stat"]["db2chr2densArr"][accDb][chr][chrBlockIndex];
}
function popupVariantCountDetail(accId, x, y, chr, pos) {
	var id2ibj = mg_getAccId2ObjMap();
	var $popup = $("#graph_popup");
	var subType = storage.getItem("_NameSub");
	if(subType == "subvariety") subType = "grp";
	var dispName = accId;
	if(storage.getItem("_Name") === "name") dispName = id2ibj[accId]["name"];
	var html = dispName + "&nbsp;&nbsp;&nbsp;" + id2ibj[accId][subType] + "<br>" +
		"<span style=\"color:gray;\">Position:</span> " + chr + ":" + pos.toLocaleString() + "<br>" +
		"<span style=\"color:gray;\">Count:</span> " + getVariantCount(id2ibj[accId]["db"], chr, pos) + "<br>" +
		"<span style=\"color:gray;\">BlockSize:</span> " + BLOCKSIZE.toLocaleString() + "bp/" + BLOCKW + "px";
	$popup.html(html);
	$popup.css("left", x + "px");
	$popup.css("top", y + "px");
	$popup.css("width", "160px");
	$popup.css("height", "auto");
	$popup.css("display", "inline-block");
}
function popupAdmixtureDetail(accId, x, y) {
	if(!admixtureInfoOri) return;

	var id2ibj = mg_getAccId2ObjMap();
	var db = id2ibj[accId]["db"];
	 if( !(db in admixtureInfoOri["data"]) ) return;

	var ks = $("#sel_chr").val();	// Always array.
	var k = scaleAdmixture.getK(XY.lastX);
	if( !(k in  admixtureInfoOri["data"][db]) ) return;

	var arr = admixtureInfoOri["data"][db][k];
	var lenColors = ADMIXTURE_COLORS.length;

	var $popup = $("#graph_popup");
	var subType = storage.getItem("_NameSub");
	if(subType == "subvariety") subType = "grp";
	var dispName = accId;
	if(storage.getItem("_Name") === "name") dispName = id2ibj[accId]["name"];
	var html = dispName + "&nbsp;&nbsp;&nbsp;" + id2ibj[accId][subType] + "&nbsp;&nbsp;&nbsp;K=" + k + "<br>";
	for(var i=0,len=arr.length; i<len; i++) {
		var colIdx = (i+1) % lenColors - 1;
		var num = i+1;
			if(String(num).length < 2) num = "&nbsp;" + num
		html += "<span style=\"color:gray;\">" + num + ":</span> <span style=\"color:" + ADMIXTURE_COLORS[colIdx] + ";\">&#9632;</span> " + 
			tasukeGraphAxis.getRoundNumber(arr[i], 6) + "<br>";
	}
	$popup.html(html);
	$popup.css("left", x + "px");
	$popup.css("top", y + "px");
	$popup.css("width", "160px");
	$popup.css("height", "auto");
	$popup.css("display", "inline-block");
}
function onRegionSelect(viewMode, tgtCvsId) {
	if((viewMode === "varcount" || viewMode === "snpeff") && ((tgtCvsId === "cvs_view" && "49" in keyState) || tgtCvsId === "cvs_scale")) {
		return true;
	} else {
		return false;
	}
}
function drawCursorLine(trigCvsId, ctx, isDragging, cnvpos) {
	if(isDragging) return;

	var cvs = ctx.canvas;
	var cvsId = cvs.getAttribute("id");
	var xl = cnvpos[0]
	var xr = cnvpos[1];
	var yt = cnvpos[2]
	var yb = cnvpos[3]	

	ctx.fillStyle = "white";

	// vertical
	if(cvsId === "cvs_view" && (trigCvsId === "cvs_view" || trigCvsId === "cvs_scale"))  ctx.fillRect(xl, 0, 1, cvs.height);

	// horizontal
	if(trigCvsId !== "cvs_scale") ctx.fillRect(0, yb+1, cvs.width, 1);
}
function initCanvasImage() {
	if(bufViewImageOri) document.getElementById("cvs_view").getContext("2d").putImageData(bufViewImageOri, 0, 0);
	if(bufTreeImageOri) document.getElementById("cvs_tree").getContext("2d").putImageData(bufTreeImageOri, 0, 0);
	if(bufTitleImageOri) document.getElementById("cvs_list").getContext("2d").putImageData(bufTitleImageOri, 0, 0);
	if(bufScaleImageOri) document.getElementById("cvs_scale").getContext("2d").putImageData(bufScaleImageOri, 0, 0);

	bufViewImage = bufViewImageOri;
	bufTreeImage = bufTreeImageOri;
	bufTitleImage = bufTitleImageOri;
	bufScaleImage = bufScaleImageOri;
}
function prevCanvasImage() {
	if(bufViewImage) document.getElementById("cvs_view").getContext("2d").putImageData(bufViewImage, 0, 0);
	if(bufTreeImage) document.getElementById("cvs_tree").getContext("2d").putImageData(bufTreeImage, 0, 0);
	if(bufTitleImage) document.getElementById("cvs_list").getContext("2d").putImageData(bufTitleImage, 0, 0);
	if(bufScaleImage) document.getElementById("cvs_scale").getContext("2d").putImageData(bufScaleImage, 0, 0);
}

function getDencityColor(chrStat, cnt) {
//	var avg = parseInt(chrStat["med"]);
	var avg = parseInt(chrStat["avg"]);

	var hUnitDenom = chrStat["maxAdjust"] - avg;
	var hUnit = 0;
		if(hUnitDenom !== 0) hUnit = 255 / hUnitDenom;
	var lUnitDenom = avg - chrStat["min"];
	var lUnit = 0;
		if(lUnitDenom !== 0) lUnit = 255 / lUnitDenom;

	var diff = cnt - avg;
	var r = 0, g = 0; b = 0;
	if(diff > 0) {
		r = 255;
		g = parseInt(255 - diff * hUnit);
		b = 0;
		if(g<0) g=0;
	} else {
		r = parseInt(255 + diff * lUnit);
		g = parseInt(255 + diff * lUnit);
		b = Math.abs(parseInt(diff * lUnit));
		if(r<0) r=0;
		if(g<0) g=0;
		if(b>255) b=255;
	}
//console.log(cnt + "/" + chrStat["min"] + "|" + avg + "|" + chrStat["maxAdjust"] + "   " +  hUnit + "," + lUnit + "   " + " => rgb(" + r + "," + g + "," + b + ")"); 
	return "rgb(" + r + "," + g + "," + b + ")";
}

function _dialog_about(){
        $("#dialog_about").dialog({
                autoOpen: true,
                modal: true,
                width: 400,
                height: 450,
                draggable: true,
                resizable: true
        });
}
function _dialog_citation(){
        $("#dialog_citation").dialog({
                autoOpen: true,
                modal: true,
                width: 650,
                draggable: true,
                resizable: true
        });
}
function _dialog_manual(){
        $("#dialog_manual_mg").dialog({
                autoOpen: true,
                modal: false,
                width: 700,
		height: 700,
                draggable: true,
                resizable: true
        });
}
function mg_dialog_apply_ok() {
	var checkedIds = [];
	if("accs" in selectedAccReg) checkedIds = getOrderedIdList(Object.keys(selectedAccReg["accs"]));
//	var checkedIds = accListManager.getCurrentCheckedId("id");
	var lenCheckedIds = checkedIds.length;
	if(lenCheckedIds === 0) return;

	var id2objMap = mg_getAccId2ObjMap();
	var dbs = [];
	for(var i=0; i<lenCheckedIds; i++) {
		if(checkedIds[i] in id2objMap) dbs.push(id2objMap[checkedIds[i]]["db"]);
	}
	storage.setItem("_Lists", dbs.join(","));
	storage.removeItem("_SysTreeNewick");
	if(lenCheckedIds < 3) {
		var en = JSON.parse(storage.getItem("_Env"));
		en.useSystemTree = "0";
		en.useSystemTreeLW = "0";
		storage.setItem("_Env", JSON.stringify(en));
	}
	location.replace('./macrogenome.html');
}
function mg_dialog_reset() {
	$("#dialog_reset").dialog({
		autoOpen: true,
		modal: true,
		width: 320,
		draggable: true,
		resizable: false
	});
}
function mg_dlg_reset_cancel() {
	$("#dialog_reset").dialog('close');
}
function mg_dlg_reset_confirm() {
	storage.clear();
	location.replace('./macrogenome.html');
}
/*
function mg_dlg_confirm_ok() {
	// Create URL.
	var url = "./?";

	// Genome reagion
	if("chr" in selectedAccReg && selectedAccReg["chr"]) url += "chr=" + selectedAccReg["chr"] + "&st=" + selectedAccReg["stPos"] + "&en=" + selectedAccReg["enPos"];

	// Accs
	var lenTargetAccs = selectedAccReg["accs"].length;
	if(lenTargetAccs > 0) {
		var originalAccs = mg_getAccListObjArray("_Lists-origin");
		if(lenTargetAccs < originalAccs.length) {
			url += "&cdb=" + tasukeCompAccArray.encodeUrlSafe(selectedAccReg["accs"].map(function(ref) {return ref.db;}), {compress:true});
		}
	}
	if(DEBUG_MODE) console.log(url);
	window.open(url, '_blank', 'noopener=true');	// Open new tab, dont extends storage values.
	mg_dlg_confirm_cancel();
}
function mg_dlg_confirm_apply() {
	var dbs = selectedAccReg["accs"].map(function(ref) {return ref.db;});
	applyDbs(dbs);
}
function mg_dlg_confirm_cancel() {
	selectedAccReg = {};
	$("#dlg_confirm").dialog('close');
}
*/
function mg_showLoadingDialog(){
	removeCvsEvents();
	$("#dialog_progress").dialog({
		autoOpen: true,
		modal: true,
		width: 300,
		classes: { "ui-dialog": "DialogNoTitle"},
		draggable: true,
		resizable: false
	});
	mg_updateProgress(0);
	$('.DialogNoTitle > .ui-dialog-titlebar').css("display", "none");
}
function mg_closeLoadingDialog(){
	$("#dialog_progress").dialog('close');

	// If don't setTimeout(), mouse events will remain.
	setTimeout(function() {
		addCvsEvents();
	}, 10);
}
function mg_updateProgress(value) {
	if(value > 100) value = 100

	var $prog = $('span[id=load_progress_bar]').find('progress');
	$prog[0].value = value; // Child elements are DOM, not jQuery.
	var $span = $('span[id=load_progress_bar]').find('span');
	$span[0].innerHTML = parseInt(value);
}

var oriProgressStr = null;
var oriProgressError = null;
function _changeProgressStr(str, error) {
	var $p = $('#dialog_progress').find('p');
	if(!oriProgressStr) oriProgressStr = $p[0].innerHTML;
	$p[0].innerHTML = str;

	var elemProgressError = document.getElementById("progress_error");
	if(!oriProgressError) oriProgressError = elemProgressError.innerHTML;
	if(error) elemProgressError.innerHTML = error;
}
function _defProgressStr() {
	if(oriProgressStr) _changeProgressStr(oriProgressStr, oriProgressError);
}

// Use graph caching image and only update selections. 
function mg_openAccListDialog() {
	if($("#div_select_idlist").is(":visible")) {
		$("#div_select_idlist").dialog('close');
	} else {
		accListManager.clearHistory();
		accListManager.clearAllFilter();
		accListManager.table.deselectRow();
		accListManager.table.setSort('num', 'asc');
		accListManager.scrollToTop();
		$("#div_select_idlist").dialog({
			autoOpen: true,
			modal: false,
			width: 810,
			height:540,
			draggable: true,
			resizable: true
		});
		$('#mg_std_AllRows').text(mg_getAccListObjArray('_Lists').length);

		syncMg2AccListTabulator();
	}
}
function mg_clearAllSelection() {
	if("accs" in selectedAccReg || "chr" in selectedAccReg) {
		selectedAccReg = {};
		if(graphPcaA) graphPcaA.clearSelectedIds();
		if(graphPcaB) graphPcaB.clearSelectedIds();
		reflectSelectionWhole();
	} else {
		return void(0);
	}
}
function mg_clearRegionSelection() {
        if( !("chr" in selectedAccReg) ) return void(0);

	delete selectedAccReg["chr"];
	delete selectedAccReg["stPos"];
	delete selectedAccReg["enPos"];
	reflectSelectionWhole();
}
function mg_clearAccSelection() {
	if( !("accs" in selectedAccReg) ) return void(0);

	delete selectedAccReg["accs"];
	if(graphPcaA) graphPcaA.clearSelectedIds();
	if(graphPcaB) graphPcaB.clearSelectedIds();
	reflectSelectionWhole();
}
function drawSelectionWhole() {
	if($("#sel_viewmode").val() === "pca") {
		if(graphPcaA) {
			graphPcaA.setSelectedIds(selectedAccReg["accs"]);
//			graphPcaA.selectedIds = getMapClone(selectedAccReg["accs"]);
			graphPcaA.draw();
		}
		if(graphPcaB) graphPcaB.draw();
		pcaOnselectFunc();	// Exec addHistory(), updateSelectedIdsForm();
	} else {
		initCanvasImage();
		drawViewSelected();
		addHistory();
		updateSelectedIdsForm();
	}
}
function reflectSelectionWhole() {
	drawSelectionWhole();
	if($("#div_select_idlist").is(":visible")) syncMg2AccListTabulator();
}

function adjustBlockHForFirefox() {
	if(!isFirefox()) return;

	var $selBlockH = $("#sel_blockh");

	var currentH = parseInt($selBlockH.val());
	var numAccs = mg_getAccListObjArray('_Lists').length;
	if(!validFormSizeForFirefox(numAccs*currentH)) {
		var blockHs = [];
		$("#sel_blockh option").each(function() {
			blockHs.push( parseInt($(this).val()) );
		});
		var newH = 1;
		for(var i=blockHs.length-1; i>=0; i--) {
			if(validFormSizeForFirefox(numAccs * blockHs[i])) {
				newH = blockHs[i];
				break;
			}
		}
		$selBlockH.val(newH);
		console.error('Max form size for Firefox has been exceeded. Adjust _BlockH :' + currentH + ' => ' + newH);
	}
}
function validFormSizeForFirefox(size) {
	return size <= 32767;
}

///////////////////////////////////////////////////////////
// Interface group for set/get accession list of Web Storage.
///////////////////////////////////////////////////////////
// [!!!Notice!!!] Objects below should not be changed after initialization by setter.
// Do not use these objects directly outside these interface group.
var accListObjArray = null;
var accListObjMap = null;	// Share ListsObj with accListObjArray to save memory.
var acc2IndexMap = null;	// Share ListsObj with accListObjArray to save memory.
function mg_setAccListObjArray(key, accObjArray) {
	if( !(accObjArray instanceof Array) ) {
		console.error("This object is not an Array");
		return;
	}

	if(key === "_Lists-origin") {
		var accObjArrayStr = JSON.stringify(accObjArray);
		accListObjArray = JSON.parse(accObjArrayStr);	// Deep copy of accObjArray
		accListObjMap = null;
		_setCompressedData(key, accObjArrayStr);
	} else {
		var dbArray = [];
		for(var i=0, len=accObjArray.length; i<len; i++) {
			var accdb = accObjArray[i].db;
			dbArray.push(accdb);
			if(key === '_Lists_deleted') setInvalidTrackRow(accdb);
		}
		storage.setItem(key, dbArray.join(','));

		if(key === "_Lists") mg_makeAcc2IndexMap(accObjArray);
/*
		if(key === "_Lists") acc2IndexMap = {};
		var dbArray = [];
		for(var i=0, len=accObjArray.length; i<len; i++) {
			var accdb = accObjArray[i].db;
			dbArray.push(accdb);
			if(key === '_Lists_deleted') {
				setInvalidTrackRow(accdb);
			} else if(key === '_Lists') {
				acc2IndexMap[accObjArray[i].id] = i;
			}
		}
		storage.setItem(key, dbArray.join(','));
*/
	}
}
function mg_getAccListObjArray(key) {
	var accObjArray = [];
	if(key === "_Lists-origin") {
		if(accListObjArray === null) {
			// For reload(F5)
			var decompData = _getCompressedData(key);
			accListObjArray = JSON.parse(decompData);
			accListObjMap = null;
		}
		accObjArray = accListObjArray;	// [Notice] Shallow copy!!
	} else {
		var data = storage.getItem(key);
		if(data) {
			var accDb2ObjMap = mg_getAccDb2ObjMap();
			var dbArray = data.split(',');
			for(var i=0, len=dbArray.length; i<len; i++) {
				accObjArray.push(accDb2ObjMap[dbArray[i]]);
			}
		}
	}
	return accObjArray;
}
function mg_makeAcc2IndexMap(accObjArray) {
	acc2IndexMap = {};
	for(var i=0, len=accObjArray.length; i<len; i++) {
		acc2IndexMap[accObjArray[i].id] = i;
	}
}
function mg_getAccDb2ObjMap() {
	return mg_getAccXX2ObjMap("db");
}
function mg_getAccId2ObjMap() {
	return mg_getAccXX2ObjMap("id");
}
function mg_getAcc2IndexMap() {
	return acc2IndexMap;
}
function mg_getAccXX2ObjMap(key) {
	var accDb2ObjMap = {};
	if(accListObjArray === null) {
		if(DEBUG_MODE) console.log("_getAccDb2ObjMap: renew accListObjArray");
		var dummyMap = mg_getAccListObjArray("_Lists-origin");	// set accListObjArray
	}
	for(var i=0,len=accListObjArray.length; i<len; i++) {
		accDb2ObjMap[accListObjArray[i][key]] = accListObjArray[i];
	}
	return accDb2ObjMap;
}
function mg_clearAccListObjArray() {
	accListObjArray = null;
	accListObjMap = null;
}

// Tools
function binstrToUint8array(bstr) {
	return Uint8Array.from(Array.prototype.map.call(bstr, function(x) {
		return x.charCodeAt(0);
	}));
}
/*
var overflowObject = new Object();
var compressedStorage = true;
//var compressedStorage = false;
function mg_setCompressedData(key,val){
        if(compressedStorage == true){
                try{
                        storage.setItem(key,LZString.compressToUTF16(val));
                }catch(e){
                        console.log(e.message);
                        setTimeout(_dlg_reset_confirm(),5000);
                }
        }else{
                storage.setItem(key,val);
        }
}

function mg_getCompressedData(key){
        var val = "";
        if(compressedStorage == true){
                if (storage.getItem(key) === null) {
                        if(key in overflowObject){
                                //alert("Invalid key was found. The TASUKE will be reset after 10 second.");
                                console.log(e.message);
                                //setTimeout(_dlg_reset_confirm(),5000);
                                return;
                        }else{
                                //val = overflowObject[key];
                        }
                }else{
                        val = LZString.decompressFromUTF16(storage.getItem(key));
                }
        }else{
                val = storage.getItem(key);
        }
        return val;
}
*/
function _isEmpty(value){
	return (value !== 0 && !value);
//	return (value == null || value.length == 0);
}


// Matcher for Acc search(select2)
function mg_customMatcher(params, data, searchStrPre) {
	if ($.trim(params.term) === '') return data;
	if (typeof data.text === 'undefined') return null;

	var termLc = params.term.toLowerCase();
	var textLc = data.text.toLowerCase();
	var modifiedData;
	if (textLc.indexOf(termLc) > -1) {
		modifiedData = $.extend({}, data, true);
		return modifiedData;
	}
	if (searchStrPre === null || searchStrPre === void 0) return null;
	var textExtLc = searchStrPre.toString().toLowerCase();

	if (textExtLc.indexOf(termLc) > -1) {
		modifiedData = $.extend({}, data, true);
		return modifiedData;
	}
	return null;
}

// Genome scale object.
var genomesScale =  function(cvs) {
	this.DEBUG_MODE = false;
	this.cvs = cvs;
	if(this.cvs) this.ctx = this.cvs.getContext('2d');
	this.init();
	this.canvasH = 14;
	this.style = {
		border: "white",
		divline: "black"
	};
	
}
genomesScale.prototype = {
	init: function() {
		this.canvasW = 0;
		this.blockSize = null;
		this.blockW = null;

		this.chrInfoMap = [];
		this.chrnum2chr = {};
		this.chr2chrnum = {};
		this.chrnum2BlockDiv = {};
		this.block2chrreg = [];
		this.bufImage = null;
	},
	setData: function(chrInfoArr, chr2blocks, blockW, blockSize) {
		// chrInfoArr contains only what is to be displayed.
		this.init();
		this.blockW = blockW;
		this.blockSize = blockSize;
		var block = 0;
		for(var j=0,jLen=chrInfoArr.length; j<jLen; j++) {
			var chrId = chrInfoArr[j].id;
			this.chrInfoMap[chrId] = chrInfoArr[j];
			this.canvasW += chr2blocks[chrId] * blockW;
			this.chrnum2chr[j] = chrId;
			this.chr2chrnum[chrId] = j;
			this.chrnum2BlockDiv[j] = [block];
			for(var k=0; k<chr2blocks[chrId]; k++) {
				this.block2chrreg[block] = [j, k];
				block++;
			}
			this.chrnum2BlockDiv[j][1] = block;
		}
		this.cvs.setAttribute("width", this.canvasW);
		this.cvs.setAttribute("height", this.canvasH);
		this.cvs.style.width = this.canvasW + "px";
		this.cvs.style.height = this.canvasH + "px";
	},
	getChrreg: function(x) {
		return this.block2chrreg[Math.floor(x / this.blockW)];
	},
	draw: function(en = {}) {
		var chrnums = Object.keys(this.chrnum2chr);
		chrnums.sort(function(a,b) {
			return parseInt(a) - parseInt(b);
		});
		this.ctx.clearRect(0, 0, this.cvs.width, this.cvs.height);
		var left = 0;
		var arc = 6;
		var ctx = this.ctx;
		for(var i=0,len=chrnums.length; i<len; i++) {
			var chr = this.chrnum2chr[chrnums[i]];
			var chrInfo = this.chrInfoMap[chr];
			var stBlock = this.chrnum2BlockDiv[chrnums[i]][0];
			var enBlock = this.chrnum2BlockDiv[chrnums[i]][1] - 1;

			var chrW = (enBlock-stBlock+1) * this.blockW;
			var right = left + chrW - 1;
			if(chrInfo.centromere > 0){
				var stCent = Math.round(chrInfo.centromere / chrInfo.size * chrW);
				ctx.fillStyle = this.style.border;
				ctx.fillRect(left+stCent-5, 1, 10, 12);

				ctx.beginPath();
				ctx.moveTo(left+arc, 1);
				ctx.lineTo(left+stCent-5,1);
				ctx.lineTo(left+stCent+5,13);
				ctx.lineTo(right-arc, 13);
				ctx.arcTo(right, 13, right, 5, arc);
				ctx.arcTo(right, 1, right-5, 1, arc);
				ctx.lineTo(left+stCent+5,1);
				ctx.lineTo(left+stCent-5,13);
				ctx.lineTo(left+arc, 13);
				ctx.arcTo(left, 13, left, 5, arc);
				ctx.arcTo(left, 1, left+arc, 1, arc)
			} else {
				ctx.beginPath();
				ctx.moveTo(left+arc, 13);
				ctx.lineTo(right-arc, 13);
				ctx.arcTo(right, 13, right, 5, arc);
				ctx.arcTo(right, 1, right-5, 1, arc);
				ctx.lineTo(left+arc, 1);
				ctx.arcTo(left, 1, left, 13, arc);
				ctx.arcTo(left, 13, left+arc, 13, arc);
			}
			ctx.closePath();
			ctx.fillStyle = _setColor().chrcolor;
			ctx.fill();
			ctx.lineWidth=1;
			ctx.strokeStyle = this.style.border;
			ctx.stroke();
			ctx.font="10px 'Helvetica'";
			ctx.textAlign = 'left';
			ctx.textBaseline = 'top';
			ctx.fillStyle = _setColor().chrstringcolor;
			ctx.fillText(en.chromosome_name + chr, left+5, 2);
			left += chrW;
		}
		if(this.cvs.width > 0) this.bufImage = ctx.getImageData(0, 0, this.cvs.width, this.cvs.height);
	},
	drawBuf: function() {
		if(this.bufImage) this.ctx.putImageData(this.bufImage, 0, 0);
	},
	drawChrDivLine: function(cvsView) {
		var chrnums = Object.keys(this.chrnum2BlockDiv);
		chrnums.sort(function(a,b) {
			return parseInt(a) - parseInt(b);
		});
		var ctx = cvsView.getContext("2d");
		ctx.fillStyle = this.style.divline;
		for(var i=0,len=chrnums.length-1; i<len; i++) {
			var enBlock = this.chrnum2BlockDiv[chrnums[i]][1];
			ctx.fillRect(enBlock*this.blockW-1, 0, 1, cvsView.height);
		}
	},
	getChrPos: function(XY) {
		// XY = {chrreg:[chrnum, chrBlock], x1:null, y1:null, x2:null, y2:null, lastX:null, lastY:null};
		var xl, xr;
		var chrStPos = null;
		var chrEnPos = null;
		var lastChrPos = null;

		var block1 = this.block2chrreg[Math.floor(XY.x1 / this.blockW)];
		if(block1) {
			var chr = this.chrnum2chr[block1[0]];
			var blockX2 = Math.floor(XY.x2 / this.blockW);
			if(this.block2chrreg[blockX2] && XY["chrreg"][0] !== this.block2chrreg[blockX2][0]) {
				// Across different chrs.
				if(XY.x1 < XY.x2) {
					xl = XY.x1;
					xr = this.chrnum2BlockDiv[XY.chrreg[0]][1] * this.blockW - 1;
				} else {
					xl = this.chrnum2BlockDiv[XY.chrreg[0]][0] * this.blockW;
					xr = XY.x1;
				}
			} else {
				xl = XY.x1;
				xr = XY.x2;
				if(xl > xr) [xl, xr] = [xr, xl];
			}
			var chrStX = this.chrnum2BlockDiv[XY.chrreg[0]][0] * this.blockW;
			chrStPos = Math.floor((xl - chrStX) / this.blockW * this.blockSize) + 1;
			chrEnPos = Math.ceil((xr - chrStX + 1) / this.blockW * this.blockSize);
	
			//TODO If the start and end chr are the same, the value can be reused.

			var lastChrreg = this.block2chrreg[Math.floor(XY.lastX / this.blockW)];
			var lastChrStX = this.chrnum2BlockDiv[lastChrreg[0]][0] * this.blockW;
			lastChrPos = Math.floor((XY.lastX - lastChrStX) / this.blockW * this.blockSize) + 1;
		}
		return [xl, xr, chr, chrStPos, chrEnPos, lastChrPos];
	},
	getX: function(chr, pos) {
		if( !(chr in this.chr2chrnum) ) return null;

		var chrnum = this.chr2chrnum[chr];
//console.log("Test:" + chr + "," + pos + "," + chrnum);
		var chrStBlock = this.chrnum2BlockDiv[chrnum][0];
		var block = chrStBlock + (pos / this.blockSize);
		return Math.round(this.blockW * block);
	}
}
var admixtureScale=  function(cvs) {
	this.DEBUG_MODE = false;
	this.cvs = cvs;
	if(this.cvs) this.ctx = this.cvs.getContext('2d');
	this.init();
	this.canvasH = 14;
	this.style = {
		stroke: "white",
		fill: "#6495ED",	// #5384ff
		text: "black",
		font: "10px 'Helvetica'",
		divline: "black"
	};
}
admixtureScale.prototype = {
	init: function() {
		this.canvasW = 0;
		this.blockW = 0;
		this.ks = [];
		this.bufImage = null;
	},
	setData: function(ks=[] ,blockW) {
		this.init();
		this.blockW = blockW;
		this.ks = ks;
		this.canvasW = blockW * ks.length;
		
		this.cvs.setAttribute("width", this.canvasW);
		this.cvs.setAttribute("height", this.canvasH);
		this.cvs.style.width = this.canvasW + "px";
		this.cvs.style.height = this.canvasH + "px";
	},
	draw: function(en = {}) {
		var kmerW = this.blockW;
		var arc = 6;
		var ctx = this.ctx;
		for(var i=0,len=this.ks.length; i<len; i++) {
			var left = i * kmerW;
			var right = left + kmerW - 1;

			ctx.beginPath();
			ctx.moveTo(left+arc, 13);
			ctx.lineTo(right-arc, 13);
			ctx.arcTo(right, 13, right, 5, arc);
			ctx.arcTo(right, 1, right-5, 1, arc);
			ctx.lineTo(left+arc, 1);
			ctx.arcTo(left, 1, left, 13, arc);
			ctx.arcTo(left, 13, left+arc, 13, arc);
			ctx.closePath();
			ctx.fillStyle = this.style.fill;
			ctx.fill();
			ctx.lineWidth=1;
			ctx.strokeStyle = this.style.stroke;
			ctx.stroke();
			ctx.font= this.style.font;
			ctx.textAlign = 'left';
			ctx.textBaseline = 'top';
			ctx.fillStyle = _setColor().chrstringcolor;
			ctx.fillText("K=" + this.ks[i], left+5, 2);
		}
		if(this.cvs.width>0) this.bufImage = ctx.getImageData(0, 0, this.cvs.width, this.cvs.height);
	},
	drawKDivLine: function(cvsView) {
		var ctx = cvsView.getContext("2d");
		ctx.fillStyle = this.style.divline;
		for(var i=0,len=this.ks.length-1; i<len; i++) {
			var enX = this.blockW * (i+1) - 1;
			ctx.fillRect(enX, 0, 1, cvsView.height);
		}
	},
	drawBuf: function() {
		if(this.bufImage) this.ctx.putImageData(this.bufImage, 0, 0);
	},
	getK: function(x) {
		var idx = Math.floor(x / this.blockW);
		return this.ks[idx];
	},
	getX: function(k) {
		// Dummy
	}
}
