var my_version = "cna_fft.js=0.2.1"; if (ver_info_array){ver_info_array.push(my_version)} else {var ver_info_array = [my_version];};

function img_on_ctx(im,ctx,x,y,w,h) {
	let img = new Image();
	img.addEventListener('load', function(){
		ctx.drawImage(img,x,y,w,h);
	}, false );
	img.src = im;
}

function info_4_span() {
	let msg;
	if (type_is_wf() ) {
		msg = "Time span to show on the graph.";
		msg += "\n\nThe width of the chart area is 620 pixels."
		msg += "  The span you select determines the amount of sample time that fits into the width."
		msg += "\n\nBy adjusting the \"Start\", you can move the \"chart window\" to view the part of the samples you want."
	} else {
		msg = "Frequency span to show on the graph.";
		msg += "\n\nThe width of the chart area is 620 pixels."
		msg += "  The span you select determines the frequency range of the FFT results that fit the width."
		msg += "  This means that the resolution of the FFT is also changed."
		msg += "\n\nBy adjusting the \"Start\", you can move the \"chart window\" to view the part of the frequency range you want."
	}
	alert(msg);
}

let fft_count=null,chunk_len=null;
function info_4_minmax() {
	let msg;
	msg = "Show min-max of the FFT results.";
	msg += "\n\nThe FFT ";
	msg += fft_count ? "was done "+fft_count+" times": "will be done multiple times";
	msg += " by dividing full 32,000 samples into overwrapping chunks ";
	msg += chunk_len ? "of "+chunk_len+" samples,": "";
	msg += " as to obtain stable result.";
	msg += "\nBy default, you can see the average of these FFT results.";
	msg += "\nBy choosing this option, you can see the min-max range of these FFT results as well.";
	alert(msg);
}

let pdef;
function get_point_def(x) {
	pdef = null;

	let xhr = new XMLHttpRequest();
	xhr.onload = function () {
		if(xhr.status != 200) {
			if(xhr.status == 0) {
				alert("ERROR@get_point_def: Unknown\n"
							+"Server response not received.\nWe might have lost connection.");
			} else {
				alert("ERROR@get_point_def["+xhr.status+"]: "+xhr.responseText);
			}
			if (failcb) failcb();
		} else {
			let t = xhr.responseText;
			let isnum = /\d/,larray;
			// let lines = t.split("\n");
			pdef = {};
			for (let i=0,l; l=t.split("\n")[i]; i++) {
				if (isnum.test(l.substring(0,1) ) ) {
					larray = l.split(",");
					pdef[larray[0] ] = larray.slice(1);
				}
			}
			parse_dl_data(x);
		}
	};
	xhr.ontimeout = function () {
		alert("Timeout@get_point_def");
		if (failcb) failcb();
	};
	xhr.open("GET", "/cnf/pnt.txt");
	xhr.timeout = 5000;	// 5 s
	xhr.setRequestHeader('Pragma', 'no-cache');									// Generic header field in HTTP/1.0
	xhr.setRequestHeader('Cache-Control', 'no-cache');							// Cache control header field in HTTP/1.1
	xhr.setRequestHeader('If-Modified-Since', 'Thu, 01 Jun 1970 00:00:00 GMT');	// Work-around for IE
	xhr.send();
}

function calc_envelope2(y,out) {
	let N = out.length;
	let e_r = new Float64Array(32768);
	let e_i = new Float64Array(32768);
	// copy
	for (let i=0; i<32768; i++) {
		if (i < N) {
			e_r[i] = y[i];
		} else {
			e_r[i] = 0;
		}
		e_i[i] = 0;
		// if (i == 0) console.log(y[i],e_r[i] );
	}

	// console.log("N",N,"y", y, "e_r", e_r, "e_i", e_i);

	// fft
	fft_ex(e_r, e_i, 32768);

	// console.log("y", y, "e_r", e_r, "e_i", e_i);

	// zero negativ frequency elements
	for (let i=16384; i<32768; i++) {
		e_r[i] = 0;
		e_i[i] = 0;
	}

	// ifft
	fft_ex(e_r, e_i, 32768, true);

	// take absolute value of complex number
	// out[0] = Math.sqrt(Math.pow(e_r[0],2) + Math.pow(e_i[0],2) );	// old -- maybe wrong
	out[0] = Math.pow(e_r[0],2) + Math.pow(e_i[0],2);
	for (let i=1; i<N; i++) {
		out[i] = 4 * (Math.pow(e_r[i],2) + Math.pow(e_i[i],2) );
	}

	// console.log("out", out);

}

function calc_env_xyz(x,y,z,out) {
	let N = out.length;
	let xyz = new Array(3);
	let wk = new Float64Array(N);
	xyz[0] = x;
	xyz[1] = y;
	xyz[2] = z;

	// console.log("xyz",0);
	calc_envelope2(x,out);
	for (let i=1; i<3; i++) {
		// console.log("xyz",i,"out",out,"N",N,"wk.length",wk.length);
		if (xyz[i] ) calc_envelope2(xyz[i],wk);
		for (let j=0; j<N; j++) out[j] += wk[j];
	}

	// Sqrt
	for (let i=0; i<N; i++) out[i] = Math.sqrt(out[i] );
}

function read_sample_file(okcb,failcb) {
	// Don't read conanair if this was launched from local file.
	if (document.URL.toLowerCase().startsWith('file') ) {
		return;
	}

	const canvas = document.getElementById("canvas");
	const context = canvas.getContext("2d");
	context.textAlign = "start";
	context.font = '12px sans-serif';

	// console.log("read_sample_file");
	let xhr = new XMLHttpRequest();
	xhr.onload = function () {
		// console.log("xhr.onload@read_sample_file");
		if(xhr.status != 200) {
			if(xhr.status == 0) {
				alert("ERROR@read_sample_file: Unknown\n"
							+"Server response not received.\nWe might have lost connection.");
			} else if (xhr.status == 404){
				alert("Measurement seems not available (status = 404.)" );
			} else {
				alert("ERROR@read_sample_file["+xhr.status+"]" );
			}

			// Clear Camvas & put message
			context.clearRect(0, 0, canvas.width, canvas.height);
			if (xhr.status == 404){
				context.fillStyle = 'rgba(255,128,0,1.0)';
				context.fillText("Waiting for the measurement data... not ready",10,15);
			} else {
				context.fillStyle = 'rgba(255,0,0,1.0)';
				context.fillText("Waiting for the measurement data... download fail!",10,15);
			}

			if (failcb) failcb();
		} else {
			let buf = xhr.response;
			// Clear Camvas
			context.clearRect(0, 0, canvas.width, canvas.height);

			if (buf.byteLength != 128512) {
				// put message
				context.fillStyle = 'rgba(255,0,0,1.0)';
				context.fillText("Waiting for the measurement data... bad data!",10,15);
				return;
			}
			// put message
			context.fillStyle = 'rgba(0,128,0,1.0)';
			context.fillText("Waiting for the measurement data... data OK, start making graph.",10,15);
			if (okcb) window.setTimeout ( function() {okcb(buf); }, 100);
		}
	};
	xhr.ontimeout = function () {
		alert("Timeout@read_sample_file");

		// Clear Camvas & put message
		context.clearRect(0, 0, canvas.width, canvas.height);
		context.fillStyle = 'rgba(255,0,0,1.0)';
		context.fillText("Waiting for the measurement data... timed-out!",10,15);

		if (failcb) failcb();
	};
	xhr.open("GET", "/.result/dummy.cna");
	xhr.timeout = 15000;	// 15 s
	xhr.responseType = "arraybuffer";
	xhr.setRequestHeader('Pragma', 'no-cache');									// Generic header field in HTTP/1.0
	xhr.setRequestHeader('Cache-Control', 'no-cache');							// Cache control header field in HTTP/1.1
	xhr.setRequestHeader('If-Modified-Since', 'Thu, 01 Jun 1970 00:00:00 GMT');	// Work-around for IE
	xhr.send();

	// Clear Camvas & put message
	context.clearRect(0, 0, canvas.width, canvas.height);
	context.font = '12px sans-serif';
	context.fillStyle = 'rgba(0,0,0,1.0)';
	context.fillText("Waiting for the measurement data...",10,15);
}

let dldata_ID,
	dldata_FN,
	dldata_MSG,
	dldata_DBL,
	dldata_PACKED,
	dldata_a=null,
	dldata_v=null,
	dldata_h=null;
let summary_ok = false,
	sum_acc_rms_x,sum_acc_rms_y,sum_acc_rms_z,sum_acc_rms_3d,
	sum_acc_pk_x, sum_acc_pk_y, sum_acc_pk_z, sum_acc_pk_3d, 
	sum_vel_rms_x,sum_vel_rms_y,sum_vel_rms_z,sum_vel_rms_3d,
	sum_vel_pk_x, sum_vel_pk_y, sum_vel_pk_z, sum_vel_pk_3d;
let N_SAMPLES;
let va3d = null, c_freq = null;
let pnt_data;
let raw_xyz = null, acc_xyz = null, vel_xyz = null, lf_xyz = null;
let env_xyz = null;
let data_souce = "Accel";

function on_data_src() {
	data_souce = document.getElementById("data_src").value;
	if (!PREV_DATA_WAS_LF && data_souce == "LF_A") {
		PREV_DATA_WAS_LF = true;
		update_range_minimax_lpf("on_data_src_1");

		// disable envelope selections
		document.getElementById('fft_type').options[3].disabled = true;
		document.getElementById('fft_type').options[4].disabled = true;
		document.getElementById('fft_type').options[5].disabled = true;
		document.getElementById('fft_type').options[6].disabled = true;
	} else if (PREV_DATA_WAS_LF && data_souce != "LF_A"){
		PREV_DATA_WAS_LF = false;
		update_range_minimax_lpf("on_data_src_2");

		// enable envelope selections
		document.getElementById('fft_type').options[3].disabled = false;
		document.getElementById('fft_type').options[4].disabled = false;
		document.getElementById('fft_type').options[5].disabled = false;
		document.getElementById('fft_type').options[6].disabled = false;
	}

	if (acc_xyz && vel_xyz&& lf_xyz) test();
}

function lpf(y) {
	let fc = Number(document.getElementById('lpf_fc').value);
	let src = document.getElementById('data_src').value;
	if (fc == 0 || src != "LF_A") return y;

	let N = y.length;
	let e_r = new Float64Array(32768);
	let e_i = new Float64Array(32768);

	let ac = fc >= 10000;
	if (ac)  fc -= 10000;

	if (fc) {
		// copy y as the real part
		for (let i=0; i<N; i++) {
			e_r[i] = y[i];
		}

		// calculate coefficients of slant tail formula
		let a = (y[0]-y[N-1] ) / (32768-N);	// inclination from end to start
		let b = y[N-1];						// intercept 

		// append slant tail
		for (let i=0; i<(32768-N); i++) {
			e_r[i+N] = a * i + b;
		}

		// fft
		fft_ex(e_r, e_i, 32768);

		// LPF 
		let fc_idx = Math.round(32768 * fc / 3200 / c_freq);
		for (let i=fc_idx; i<(32768-fc_idx); i++) {
			e_r[i] = 0;
			e_i[i] = 0;
		}

		if (ac) {	// remove DC with FFT[0] = 0
			e_r[0] = 0;
			e_i[0] = 0;
		}

		// ifft
		fft_ex(e_r, e_i, 32768, true);

		// check imag part magnitude
		let i_min = Math.min.apply(null, e_i);
		let i_max = Math.max.apply(null, e_i);
		if (i_min < -0.02 || i_max > 0.02)
			console.log("WARNING: Large imag in LPF:",i_min,i_max);
	} else {	// just remove dc
		// calculate mean as dc component
		let dc = 0;
		for (let i=0; i<N; i++)
			dc += y[i];
		dc /= N;

		// copy to e_r buffer with removing dc
		for (let i=0; i<N; i++) {
			e_r[i] = y[i] - dc;
		}
	}

	return e_r.slice(0, N);
}


function lpf_old(y) {
	let fc = Number(document.getElementById('lpf_fc').value);
	if (fc == 0) return y;

	let ac = fc >= 10000;
	if (ac) fc -= 10000;

	let N = y.length;
	let e_r = new Float64Array(32768);
	let e_i = new Float64Array(32768);

	// calculate mean as dc component
	let dc = 0;
	for (let i=0; i<N; i++)
		dc += y[i];
	dc /= N;

	// calculate coefficients of slant tail formula
	let a = (y[0]-y[N-1] ) / (32768 - N);	// inclination from end to start
	let b = y[N-1];							// intercept 
	if (ac) {
		b -= dc;
	} else {
		dc = 0;
	}

	// copy
	for (let i=0; i<32768; i++) {
		if (i < N) {
			e_r[i] = y[i] - dc;
		} else {
			e_r[i] = a * (i - N) + b;
		}
		e_i[i] = 0;
	}

	if (fc) {
		// fft
		fft_ex(e_r, e_i, 32768);

		let fc_idx = Math.round(32768 * fc / 3200 / c_freq);
		for (let i=fc_idx; i<(32768-fc_idx); i++) {
			e_r[i] = 0;
			e_i[i] = 0;
		}

		// ifft
		fft_ex(e_r, e_i, 32768, true);

		let i_min = Math.min.apply(null, e_i);
		let i_max = Math.max.apply(null, e_i);
		if (i_min < -0.02 || i_max > 0.02)
			console.log("WARNING: Large imag in LPF:",i_min,i_max);
	}

	return e_r.slice(0, N);
}


function parse_dl_data(buf) {
	// console.log("parse_dl_data",buf);

	// Global
	dldata_ID =  String.fromCharCode.apply("", new Uint8Array(buf.slice(0,10) ) ).split('\0')[0];
	dldata_FN =  String.fromCharCode.apply("", new Uint8Array(buf.slice(10,31) ) ).split('\0')[0];
	dldata_MSG = String.fromCharCode.apply("", new Uint8Array(buf.slice(31,64) ) ).split('\0')[0];
	dldata_DBL =    new Float64Array(buf.slice(64,512) );
	dldata_PACKED = new Uint32Array(buf.slice(512) );
	dldata_a=new Array(4);
	dldata_v=new Array(4);
	dldata_h=new Array(4);
	N_SAMPLES = dldata_PACKED.length;

	// Global for summary
	summary_ok = true;
	sum_acc_pk_x   = (dldata_DBL[5] - dldata_DBL[6] ) / 2.0;
	sum_acc_pk_y   = (dldata_DBL[7] - dldata_DBL[8] ) / 2.0;
	sum_acc_pk_z   = (dldata_DBL[9] - dldata_DBL[10] ) / 2.0;
	sum_acc_pk_3d  = dldata_DBL[11];
	sum_acc_rms_x  = dldata_DBL[12];
	sum_acc_rms_y  = dldata_DBL[13];
	sum_acc_rms_z  = dldata_DBL[14];
	sum_acc_rms_3d = dldata_DBL[15];
	sum_vel_pk_x   = (dldata_DBL[16] - dldata_DBL[17] ) / 2.0;
	sum_vel_pk_y   = (dldata_DBL[18] - dldata_DBL[19] ) / 2.0;
	sum_vel_pk_z   = (dldata_DBL[20] - dldata_DBL[21] ) / 2.0;
	sum_vel_pk_3d  = dldata_DBL[22];
	sum_vel_rms_x  = dldata_DBL[23];
	sum_vel_rms_y  = dldata_DBL[24];
	sum_vel_rms_z  = dldata_DBL[25];
	sum_vel_rms_3d = dldata_DBL[26];
	switch_show_summary();

	pnt_data = null;
	if (pdef) pnt_data = pdef[parseInt(dldata_FN.slice(0,2) ) ];
	// console.log(pdef,pnt_data,dldata_FN,dldata_FN.slice(0,2),parseInt(dldata_FN.slice(0,2) ) );
	// if (pdef) pnt_data = pdef[9999];
	// console.log(pnt_data);

	// const
	const odr = dldata_DBL[41];	// Output Data Rate
	const k = 4e-3*9.8;			// LSB to m/s/s conversion
	const dt = 1e3/odr;			// delta-T in ms

	let iir_k1=dldata_DBL[27],
		iir_k2=dldata_DBL[28],
		iir0=new Float64Array(3),
		iir1=new Float64Array(3),
		iir2=new Float64Array(3),
		velo=new Float64Array(3);
	iir0[0]=dldata_DBL[29];	// x0
	iir1[0]=dldata_DBL[30];	// x1
	iir2[0]=dldata_DBL[31];	// x2
	iir0[1]=dldata_DBL[33];	// y0
	iir1[1]=dldata_DBL[34];	// y1
	iir2[1]=dldata_DBL[35];	// y2
	iir0[2]=dldata_DBL[37];	// z0
	iir1[2]=dldata_DBL[38];	// z1
	iir2[2]=dldata_DBL[39];	// z2
	velo[0]=k * dldata_DBL[32];	// x
	velo[1]=k * dldata_DBL[36];	// y
	velo[2]=k * dldata_DBL[40];	// z
	for (let i=0; i<4; i++) {
		dldata_a[i] = new Float64Array(N_SAMPLES);
		dldata_v[i] = new Float64Array(N_SAMPLES);
		dldata_h[i] = new Float64Array(N_SAMPLES);
	}
	let v = new Float64Array(3);
	let signed = x => x & 0x0200 ? x - 0x400: x;

	// Go through all packed samples
	for (let i=0; i<N_SAMPLES; i++) {

	// Unpack samples and convert to m/s/s
		let p = dldata_PACKED[i];
		let exp = p >>> 30;
		v[0] = parseFloat(signed(0x000003ff & p) << exp);
		v[1] = parseFloat(signed((0x000ffc00 & p) >>> 10) << exp);
		v[2] = parseFloat(signed((0x3ff00000 & p) >>> 20) << exp);

		// Store accelerations to array
		for (let j=0; j<3; j++) dldata_a[j][i] = k*v[j];
		dldata_a[3][i] = k*Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2] );

		// console.log(dldata_a[0][i],dldata_a[1][i],dldata_a[2][i],dldata_a[3][i] );

		// IIR HPF
		dldata_h[3][i] = 0.0;
		for (let j=0; j<3; j++) {
			let h1 = v[j] - iir0[j];
			let h2 = h1 - iir1[j];
			dldata_h[j][i] = k*(h2 - iir2[j] );
			dldata_h[3][i] += dldata_h[j][i]*dldata_h[j][i];
			iir0[j] = iir_k2 * iir0[j] + iir_k1 * v[j];
			iir1[j] = iir_k2 * iir1[j] + iir_k1 * h1;
			iir2[j] = iir_k2 * iir2[j] + iir_k1 * h2;
		}
		dldata_h[3][i] =  Math.sqrt(dldata_h[3][i] );
		// console.log(dldata_h[0][i],dldata_h[1][i],dldata_h[2][i],dldata_h[3][i] );

		// Calculate velocity
		dldata_v[3][i] = 0.0;
		for (let j=0; j<3; j++) {
			velo[j] *= iir_k2;
			velo[j] += dt * dldata_h[j][i];
			dldata_v[j][i] = velo[j];
			dldata_v[3][i] += velo[j]*velo[j];
		}
		dldata_v[3][i] =  Math.sqrt(dldata_v[3][i] );
		// console.log(dldata_v[0][i],dldata_v[1][i],dldata_v[2][i],dldata_v[3][i] );
	}
	va3d = dldata_h[3];
	c_freq = 10e6 / dldata_DBL[42];

	// console.log("actual samplint time =",1e-6*dldata_DBL[42],"[s], Freq correction =",c_freq);

	// File info
	set_file_info(dldata_ID,dldata_FN,1/c_freq);

	// LPF for test
	// if (type_is_wf() ) {
		// dldata_a[0] = lpf(dldata_a[0]);
		// dldata_a[1] = lpf(dldata_a[1]);
		// dldata_a[2] = lpf(dldata_a[2]);
	// }

	// Calc. envelope
	acc_xyz = [dldata_h[0],dldata_h[1],dldata_h[2] ];
	vel_xyz = [dldata_v[0],dldata_v[1],dldata_v[2] ];
	lf_xyz =  [dldata_a[0],dldata_a[1],dldata_a[2] ];
	raw_xyz = data_souce == "Accel" ? acc_xyz: (data_souce == "Velocity" ? vel_xyz: lf_xyz);
	// env_xyz = new Float64Array(32000);
	// calc_env_xyz(dldata_h[0],dldata_h[1],dldata_h[2],env_xyz);

	// Go!
	test();
}

function read_csv(f) {
	let i,l,csv_id='unknown',csv_fn='unknown';
	let reader = new FileReader();

	// Capture the file information.
	reader.onload = function(e) {
		// Read csv and go!
		let lines = e.target.result.split('\n');


		let va_x = [],  va_y = [],  va_z = [],  va3d = [];
		let vva_x = [], vva_y = [], vva_z = [], vva3d = [];
		let la_x = [],  la_y = [],  la_z = [];
		let summary_ended = false;
		let hacc_pkxp,hacc_pkyp,hacc_pkzp,vel_pkxp,vel_pkyp,vel_pkzp;
		try {
			for (i=0; l=lines[i]; i++) {
				let tokens = l.split(',');
				if (summary_ended) {
					let lval_x = Number(tokens[0] );
					let lval_y = Number(tokens[1] );
					let lval_z = Number(tokens[2] );
					let val_x  = Number(tokens[3] );
					let val_y  = Number(tokens[4] );
					let val_z  = Number(tokens[5] );
					let value  = Number(tokens[6] );
					let vval_x = Number(tokens[7] );
					let vval_y = Number(tokens[8] );
					let vval_z = Number(tokens[9] );
					let vvalue = Number(tokens[10] );
					if (
						isNaN(lval_x) || lval_x === void 0 ||
						isNaN(lval_y) || lval_y === void 0 ||
						isNaN(lval_z) || lval_z === void 0 ||
						isNaN(val_x)  || val_x === void 0  ||
						isNaN(val_y)  || val_y === void 0  ||
						isNaN(val_z)  || val_z === void 0  ||
						isNaN(value)  || value === void 0  ||
						isNaN(vval_x) || vval_x === void 0 ||
						isNaN(vval_y) || vval_y === void 0 ||
						isNaN(vval_z) || vval_z === void 0 ||
						isNaN(vvalue) || vvalue === void 0
						) {
						alert('Invalid data found in line '+(i+1)+':\n'+l.trim() );
						throw TypeError();
					}
					va_x.push(val_x);
					va_y.push(val_y);
					va_z.push(val_z);
					va3d.push(value);
					vva_x.push(vval_x);
					vva_y.push(vval_y);
					vva_z.push(vval_z);
					vva3d.push(vvalue);
					la_x.push(lval_x);
					la_y.push(lval_y);
					la_z.push(lval_z);
				}
				if (tokens[0] == 'Ident') 		csv_id = tokens[1];
				if (tokens[0] == 'FileName') 	csv_fn = tokens[1];
				if (tokens[0] == 'Act_ms') {
					let value = Number(tokens[1] );
					if (isNaN(value) || value === void 0) {
						alert('Invalid data found in line '+(i+1)+':\n'+l+'\nvalue = '+value);
						throw TypeError();
					}
					c_freq = 10e3 / value;
				}
				if (tokens[0] == 'END_Summary') summary_ended = true;

				// Global for summary
				if (tokens[0] == 'HAccPkXP')  hacc_pkxp      = parseFloat(tokens[1] );
				if (tokens[0] == 'HAccPkXN')  sum_acc_pk_x   = (hacc_pkxp - parseFloat(tokens[1] ) ) / 2.0;
				if (tokens[0] == 'HAccPkYP')  hacc_pkyp      = parseFloat(tokens[1] );
				if (tokens[0] == 'HAccPkYN')  sum_acc_pk_y   = (hacc_pkyp - parseFloat(tokens[1] ) ) / 2.0;
				if (tokens[0] == 'HAccPkZP')  hacc_pkzp      = parseFloat(tokens[1] );
				if (tokens[0] == 'HAccPkZN')  sum_acc_pk_z   = (hacc_pkzp - parseFloat(tokens[1] ) ) / 2.0;
				if (tokens[0] == 'HAccPk3D')  sum_acc_pk_3d  = parseFloat(tokens[1] );
				if (tokens[0] == 'HAccRmsX')  sum_acc_rms_x  = parseFloat(tokens[1] );
				if (tokens[0] == 'HAccRmsY')  sum_acc_rms_y  = parseFloat(tokens[1] );
				if (tokens[0] == 'HAccRmsZ')  sum_acc_rms_z  = parseFloat(tokens[1] );
				if (tokens[0] == 'HAccRms3D') sum_acc_rms_3d = parseFloat(tokens[1] );
				if (tokens[0] == 'VelPkXP')   vel_pkxp       = parseFloat(tokens[1] );
				if (tokens[0] == 'VelPkXN')   sum_vel_pk_x   = (vel_pkxp - parseFloat(tokens[1] ) ) / 2.0;
				if (tokens[0] == 'VelPkYP')   vel_pkyp       = parseFloat(tokens[1] );
				if (tokens[0] == 'VelPkYN')   sum_vel_pk_y   = (vel_pkyp - parseFloat(tokens[1] ) ) / 2.0;
				if (tokens[0] == 'VelPkZP')   vel_pkzp       = parseFloat(tokens[1] );
				if (tokens[0] == 'VelPkZN')   sum_vel_pk_z   = (vel_pkzp - parseFloat(tokens[1] ) ) / 2.0;
				if (tokens[0] == 'VelPk3D')   sum_vel_pk_3d  = parseFloat(tokens[1] );
				if (tokens[0] == 'VelRmsX')   sum_vel_rms_x  = parseFloat(tokens[1] );
				if (tokens[0] == 'VelRmsY')   sum_vel_rms_y  = parseFloat(tokens[1] );
				if (tokens[0] == 'VelRmsZ')   sum_vel_rms_z  = parseFloat(tokens[1] );
				if (tokens[0] == 'VelRms3D')  sum_vel_rms_3d = parseFloat(tokens[1] );
			}
			if (va3d.length != 32000) {
				alert('Invalid file: length of waveform data ('+va3d.length+') != 32000');
				throw TypeError();
			}
			summary_ok = true;
		} catch (e) {
			if (!(e instanceof TypeError) ) {
				alert('Unexpected error ('+e+') in reading file '+f.name+' near line '+(i+1)+'.');
			}
			return;
		}
		switch_show_summary();

		// File info
		dldata_ID = csv_id;
		dldata_FN = csv_fn;
		set_file_info(dldata_ID,dldata_FN,1/c_freq);

		// LPF for test
		// if (type_is_wf() ) {
			// la_x = lpf(la_x);
			// la_y = lpf(la_y);
			// la_z = lpf(la_z);
		// }

		// Calc. envelope
		acc_xyz = [va_x,  va_y,  va_z];
		vel_xyz = [vva_x, vva_y, vva_z];
		lf_xyz =  [la_x,  la_y,  la_z];
		raw_xyz = data_souce == "Accel" ? acc_xyz: (data_souce == "Velocity" ? vel_xyz: lf_xyz);
		// env_xyz = new Float64Array(32000);
		// calc_env_xyz(va_x, va_y, va_z, env_xyz);

		// Go!
		test();
	};

	// Read in the csv file as text
	reader.readAsText(f);
}

function read_cna(f) {
	let i,l;
	let reader = new FileReader();

	// Capture the file information.
	reader.onload = function(e) {
		parse_dl_data(e.target.result);
	};

	// Read the cna file into ArrayBffer
	reader.readAsArrayBuffer(f);
}

function check_file_APIs() {
	if (window.File && window.FileReader && window.FileList && window.Blob) {
		// Great success! All the File APIs are supported.
		// alert('All File APIs are fully supported in this browser!');
	} else {
		alert('The File APIs are not fully supported in this browser.');
	}
}

function set_file_info(id,fn,f_err) {
	document.getElementById('file_info').innerHTML = (
		'File info: <strong>' + fn + '</strong><br/>'+
		'['
			+ id.trim() + ", " +
			'<span style="font-style:italic;">' +
			(type_is_wf() ? 'T': 'F') +
			'<sub>error</sub></span>=' + ((f_err - 1) * 100).toFixed(2) + '% (corrected)' +
		']'
		);
}

let prev_file_info;
function handleFile_sub(f) {
	if (! f) {
		alert("Bad file -- can't obtain file info."
			+ "\nYou did drag'n-drop a content of zip file from Explorer window, didn't you?");
		// Restore previous information
		document.getElementById("file_info").innerHTML = prev_file_info;
		return;
	}
	if (f.name.split('.')[f.name.split('.').length - 1].toLowerCase() == 'cna') {
		read_cna(f);
	} else if (f.name.split('.')[f.name.split('.').length - 1].toLowerCase() == 'csv') {
		read_csv(f);
	} else {
		alert('File "'+f.name+'" seems NOT neither CSV nor CNA.');
		// Restore previous information
		document.getElementById("file_info").innerHTML = prev_file_info;
		return;
	}
	document.getElementById("infile").value = '';
}

function handleFileDnD(evt) {
	evt.stopPropagation();
	evt.preventDefault();

	let files = evt.dataTransfer.files; // FileList object.

	// Cache and clear file dialog value and file info
	prev_file_info = document.getElementById("file_info").innerHTML;
	document.getElementById("file_info").innerHTML = '';

	handleFile_sub(files[0] );
}

function handleDragOver(evt) {
	evt.stopPropagation();
	evt.preventDefault();
	evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
}

// Setup the dnd listeners.
let dropZone = document.getElementById('drop_zone');
dropZone.addEventListener('dragover', handleDragOver, false);
dropZone.addEventListener('drop', handleFileDnD, false);

// file dialog handler
function handleFileSelect(evt) {
	// obtain file info.
	let infile = evt.target.files;

	// files is a FileList of File objects.
	handleFile_sub(infile[0] );
}
document.getElementById('infile').addEventListener('change', handleFileSelect, false);

const PLOT_CELLS=[620,400],CANV_SIZE=[680,435],AXES_POS=[60,25];
let peaks,pk_excl_idx;

function show_NSXe() {
	const canvas = document.getElementById("canvas");
	const context = canvas.getContext("2d");

	if (raw_xyz) {
		img_on_ctx('logo_a05.png',context,5,canvas.height-17,20,15);
		context.font = '10px sans';
		context.textAlign = 'start';
		context.textBaseline = 'alphabetic';
		context.fillStyle = 'rgba(0,48,128,0.5)';
		context.fillText("FFT@conanair", canvas.width - 150, canvas.height - 5);
		context.fillText("Natural Science eXecution", 30, canvas.height - 5);
	} else {
		img_on_ctx('logo_a05.png',context,canvas.width - 225,canvas.height - 65,40,30);
		context.font = '20px sans';
		context.textAlign = 'start';
		context.textBaseline = 'alphabetic';
		context.fillStyle = 'rgba(0,48,128,0.5)';
		context.fillText("NSXe Co. LTD", canvas.width - 165, canvas.height - 40);
		context.font = '15px sans';
		context.fillText("Natural Science eXecution", canvas.width - 220, canvas.height - 20);
	}
}

function get_peaks(data,index0,df) {
	const MIN_PEAK_PX = 2;

	pk_excl_idx = new Array(2);
	pk_excl_idx[0] = index0 > MIN_PEAK_PX ? index0 + 2: MIN_PEAK_PX;
	pk_excl_idx[1] = index0 > MIN_PEAK_PX ? index0 + PLOT_CELLS[0]: PLOT_CELLS[0];

	let start = MIN_PEAK_PX + 1;
	peaks = [];
	for (let i=start; i<PLOT_CELLS[0]; i++) {
		// let check = false;
		if (data[index0 + i - 1] > data[index0 + i - 2] && data[index0 + i - 1] > data[index0 + i] ) {
			peaks.push([data[index0 + i - 1], i - 1] );
			// check = true;
		}
		// if (i < (start+10) ) console.log(index0 + i - 2, i*df,data[index0 + i - 2], data[index0 + i - 1], data[index0 + i],check);
	}
	peaks.sort((a,b) => parseFloat(a[0] ) > parseFloat(b[0] ) ? -1:(parseFloat(a[0] ) < parseFloat(b[0] ) ? 1: 0) );
}

function get_xrange(index0,dx) {
	let x0 = index0 * dx;
	let x1 = (index0 + PLOT_CELLS[0] - 1) * dx;
	let ddiv;
	if (dx < 0.030625) {
		ddiv = 1;
	} else if (dx < 0.06125) {
		ddiv = 2;
	} else if (dx < 0.125) {
		ddiv = 5;
	} else if (dx < 0.25) {
		ddiv = 10;
	} else if (dx < 0.5) {
		ddiv = 20;
	} else if (dx < 1.0) {
		ddiv = 50;
	} else {
		ddiv = 100;
	}
	return [[x0,x1],ddiv];
}

function get_yrange(data,index0) {
	let start = Math.max(5,index0);
	let part = data.slice(start,index0 + PLOT_CELLS[0] - 1);
	let ymax = Math.max.apply(null,part);
	let range_values = [0.5,1,2,5,10,20,50,100,200];
	let range;
	let man_range = document.getElementById("v_range").value;
	if (man_range == "A") {
		for (const v of range_values) {
			if (ymax < v) {
				range = v;
				break;
			}
		}
	} else {
		range = parseFloat(man_range);
	}
	return [[0,range],[range/10,10] ];
}

function mk_graph(intensity,int_min,int_max,index0=0,dx=3200/4096,plt_pks=true,plt_minmax=false) {
	let xrange,xdiv,yrange,ydiv;
	[xrange,xdiv] = get_xrange(index0,dx);
	[yrange,ydiv] = get_yrange(plt_minmax ? int_max: intensity,index0);
	
	const axis_btm = CANV_SIZE[1] - AXES_POS[1];
	const axis_top = axis_btm - PLOT_CELLS[1];
	const axis_lft = AXES_POS[0];
	const axis_rit = axis_lft + PLOT_CELLS[0];

	const canvas = document.getElementById("canvas");
	const context = canvas.getContext("2d");

	// Get paint ranges of columns
	let cell_size_y = PLOT_CELLS[1] / (yrange[1] - yrange[0] );
	let conv_d2g = x => axis_btm - (x - yrange[0] ) * cell_size_y;
	let gdata = new Array(PLOT_CELLS[0] );
	let minmax = new Array(PLOT_CELLS[0] );
	for (let j=0; j<PLOT_CELLS[0]; j++) {	// for every graph columns
		let g_coord_y = Math.max(Math.ceil(conv_d2g(yrange[1] ) ), Math.ceil(conv_d2g(intensity[index0 + j] ) ) );
		let g_coord_min = Math.max(Math.ceil(conv_d2g(yrange[1] ) ), Math.ceil(conv_d2g(int_min[index0 + j] ) ) );
		let g_coord_max = Math.max(Math.ceil(conv_d2g(yrange[1] ) ), Math.ceil(conv_d2g(int_max[index0 + j] ) ) );
		minmax[j] = [g_coord_min,g_coord_max];
		gdata[j] = [g_coord_y,g_coord_y];
		if (j > 0) {
			if (gdata[j-1][0] > g_coord_y) {			// previous top is lower on the screen than current point
				let mid = (gdata[j-1][0] + g_coord_y) / 2;
				let org_prev=gdata[j-1][0],
					org_cur=gdata[j][1];
				gdata[j-1][0] = Math.round(mid);
				gdata[j][1] =   Math.round(mid);
			} else if (gdata[j-1][1] < g_coord_y) {	// previous is btm higher on the screen than current point
				let mid = (gdata[j-1][1] + g_coord_y) / 2;
				let org_prev=gdata[j-1][1],
					org_cur=gdata[j][0];
				gdata[j-1][1] = Math.round(mid);
				gdata[j][0] =   Math.round(mid);
				// console.log(i+","+j+", previous btm higher on the screen than current top: "+org_prev+" -> "+gdata[j-1][1]
								// +", cur_top: "+org_cur+" -> "+gdata[j][0]+", mid="+mid);
			}
		}
	}
	// Clear Camvas
	context.clearRect(0, 0, canvas.width, canvas.height);
	context.font = '12px sans-serif';
	context.textAlign = "start";
	context.textBaseline = 'alphabetic';

	// Draw frame on canvas
	context.fillStyle = 'ivory';
	context.strokeStyle = 'rgba(160,160,160,1.0)';
	context.fillRect(0, 0, canvas.width, canvas.height);
	context.strokeRect(0, 0, canvas.width, canvas.height);
	context.beginPath();
	context.lineWidth = 2;
	context.rect(axis_lft, axis_top, PLOT_CELLS[0], PLOT_CELLS[1]);
	context.closePath();
	context.stroke();

	// Unit of the Value
	const leg_pads = [5,2,5,5];	// left,top,right,between items
	const leg_height = 12;
	context.fillStyle = 'rgba(0,0,0,1.0)';
	if (data_souce == "Velocity") {
		document.getElementById("display_unit1").innerHTML = "[mm/s]";
		context.fillText("[mm/s]",leg_pads[0],3*leg_height); 
	} else {
		document.getElementById("display_unit1").innerHTML = "[m/s<sup>2</sup>]";
		context.fillText("[m/s²]", leg_pads[0], 3*leg_height); 
	}
	// Unit of time
	context.textAlign = "end";
	context.fillText("[Hz]",axis_rit,axis_btm+2*leg_height); 

	// Draw grid -- Horizontal + Tick Lables
	const gdiv_y = PLOT_CELLS[1] / ydiv[1];
	const ddiv_y = ydiv[0];
	let cur_y = axis_top;
	let cur_val = ydiv[0] * ydiv[1];
	const decimal = cur_val >= 100 ? 0: (cur_val >= 10 ? 1: 2);
	context.textAlign = "end";
	while (cur_y <= axis_btm) {
		if (cur_y > axis_top && cur_y < axis_btm) {
			context.beginPath();
			context.lineWidth = 1;
			context.strokeStyle = 'rgba(216,216,216,1.0)';
			context.moveTo(axis_lft-1,cur_y);
			context.lineTo(axis_rit,cur_y);
			context.closePath();
			context.stroke();
		}
		context.fillText(cur_val.toFixed(decimal), axis_lft - leg_pads[0], cur_y + 8*leg_height/20); 
		cur_y += gdiv_y;
		cur_val -= ddiv_y;
	}
	// Vertical + Tick Lables
	const gdiv_x = xdiv / dx;
	const ddiv_x = xdiv;	// points/div / odr
	let cur_x;
	if (index0 > 0) {
		cur_val = ddiv_x * Math.ceil(xrange[0] / ddiv_x);
		cur_x = axis_lft + Math.round((cur_val - xrange[0] ) / dx);
	} else {
		cur_val = xrange[0];
		cur_x = axis_lft;
	}
	context.textAlign = "center";
	let x_tick_cnt = 0;
	while (cur_x <= axis_rit) {
		if (gdiv_x > 40 || (x_tick_cnt % 2) == 0 ) {
			if (cur_x > axis_lft && cur_x < axis_rit) {
				context.beginPath();
				context.lineWidth = 1;
				context.strokeStyle = 'rgba(216,216,216,1.0)';
				context.moveTo(cur_x,axis_btm-1);
				context.lineTo(cur_x,axis_top);
				context.closePath();
				context.stroke();
			}
			if (cur_val < 1700) {
				context.fillText(cur_val.toFixed(0), cur_x, axis_btm + leg_height); 
			}
		}
		x_tick_cnt++;
		cur_x += gdiv_x;
		cur_val += ddiv_x;
	}

	// Draw graph
	for (let j=0; j<PLOT_CELLS[0]; j++) {
		let len,adj;

		// min-max
		if (
				plt_minmax && 
				!["120","60","30","15"].includes(document.getElementById("f_span").value)	// don't plot min-max in these spans
			) {
			context.beginPath();
			context.lineWidth = 1;
			context.strokeStyle = 'rgba(0,64,0,0.2)';	// Very dark green, alpha=0.2

			adj = minmax[j][0] == minmax[j][1] ? -1: 0;
			context.moveTo(axis_lft+j,minmax[j][0] );
			context.lineTo(axis_lft+j,minmax[j][1]+adj);

			context.closePath();
			context.stroke();
		}

		// ave
		context.beginPath();
		context.lineWidth = 1;
		context.strokeStyle = FFT_COLOR;

		len = gdata[j][1] - gdata[j][0];
		adj = len == 0 ? -2: (len == -1 ? -1: (len == 1 ? 1 :0) );
		context.moveTo(axis_lft+j,gdata[j][0] );
		context.lineTo(axis_lft+j,gdata[j][1]+adj);

		context.closePath();
		context.stroke();
	}

	// Plot peaks
	if (plt_pks) {
		let num = Math.min(plt_pks,peaks.length);

		// plot check
		// let index050 = Math.round(50/dx);
		// console.log("Plot check, num =",num);
		// peaks[num-1][1] = index050;	// x=0
		// intensity[index050] = 0.5;	// y=0.5
		// let index300 = Math.round(300/dx);
		// peaks[num-2][1] = index300;	// x=300
		// intensity[index300] = 0.5;	// y=0.5

		context.fillStyle = 'rgba(255,0,0,1.0)';
		context.font = '12px sans-serif';
		for (let i=num - 1; i>=0; i--) {
			let pk_g_coord = 	Math.ceil(conv_d2g(intensity[index0 + peaks[i][1] ] ) );
			if (pk_g_coord >= axis_top) {
				context.textBaseline = 'middle';
				context.textAlign = "center";
				context.fillText("X", axis_lft + peaks[i][1], pk_g_coord);
				context.textBaseline = 'bottom';
				context.textAlign = "start";
				context.fillText(i+1, axis_lft + peaks[i][1] + 3, pk_g_coord);
			}
		}
	}
}

function wf_xrange(index0) {
	let n_s = get_n_chunk_sum()[1];			// number to summarize
	let sdt = 10 / c_freq / 32000;			// time interval between samples
	let pdt = n_s * sdt;					// time interval between pixels
	let x0 = index0 * sdt;					// left x value in time unit
	let x1 = x0 + pdt * (PLOT_CELLS[0] - 1)	// right x value in time unit
	let xdiv;	//  X division size in time unit
	switch (n_s) {
		case 32:	// 6s
			xdiv = 1.0;
			break;
		case 16:	// 3s
			xdiv = 0.5;
			break;
		case 8:		// 1.5s
			xdiv = 0.2;
			break;
		case 4:		// 0.75s
			xdiv = 0.1;
			break;
		case 2:		// 0.38s
			xdiv = 0.05;
			break;
		case 1:		// 0.19s
			xdiv = 0.02;
			break;
		default:	// 55 = 10s
			xdiv = 2.0;
			break;
	}
	let pdiv = xdiv / pdt;	//  X division size in pixels
	return [[x0,x1],xdiv,pdiv];
}

function wf_yrange(xyz, index0,n_sum) {
	let ymax=-9999,ymin=9999;
	for (let i=0; i<3; i++) {
		if (!WF_AXES[i] ) continue;

		let part = xyz[i].slice(index0, index0 + n_sum * (PLOT_CELLS[0] - 1) );
		let wk_max = Math.max.apply(null,part);
		let wk_min = Math.min.apply(null,part);
		ymax = Math.max(wk_max, ymax);
		ymin = Math.min(wk_min, ymin);
	}
	// console.log("ymin =",ymin, "; ymax =",ymax);

	let range_values = [0.5,1,2,5,10,20,50,100,200];
	let l_range=-500, h_range=500;
	let man_range = document.getElementById("v_range").value;
	if (["A","LFA"].includes(man_range) ) {
		for (const v of range_values) {
			let div = v / 5;
			// Position near the bottom
			l_range = div * Math.floor(ymin / div);
			h_range = l_range + 2*v;
			// console.log("l_range =",l_range, "; h_range =",h_range);
			if (h_range > ymax) break;
		}
		// Reposition near the center
		let top_margin = h_range - ymax;
		let btm_margin = ymin - l_range;
		let division = (h_range - l_range) / 10;
		// console.log("wf_yrange_1",l_range,h_range,division,(top_margin - btm_margin) / division);
		while (((top_margin - btm_margin) / division) > 2) {
			h_range -= division;
			l_range -= division;
			top_margin = h_range - ymax;
			btm_margin = ymin - l_range;
			// console.log("wf_yrange_2",l_range,h_range,division,(top_margin - btm_margin) / division);
		}
	} else {
		h_range = parseFloat(man_range);
		l_range = -h_range;
	}
	return [[l_range,h_range],[(h_range-l_range)/10,10] ];
}

let wf_xyz = null;
function wf_graph(index0=0,n_sum=64) {

	let xrange,xdiv,pxdiv;
	[xrange,xdiv,pxdiv] = wf_xrange(index0);

	let yrange,ydiv;
	let range = document.getElementById("v_range").value;
	if (range != "LFA") 
		[yrange,ydiv] = wf_yrange(raw_xyz,index0,n_sum);

	wf_xyz = new Array(3);
	for (let i=0; i<3; i++) {
		wf_xyz[i] = lpf(raw_xyz[i] );
	}

	// show raw results for WF
	if (document.getElementById("show_all").value == 'show') {
		let all_res = document.getElementById("all_result");
		let wait_res = document.getElementById("wait_result");
		wait_res.style.visibility = 'visible';
		setTimeout(function(){
			show_all_results_table();
			all_res.style.display = 'block';
			wait_res.style.visibility = 'hidden';
		}, 5);
	}

	if (range == "LFA") 
		[yrange,ydiv] = wf_yrange(wf_xyz,index0,n_sum);
	
	const axis_btm = CANV_SIZE[1] - AXES_POS[1];
	const axis_top = axis_btm - PLOT_CELLS[1];
	const axis_lft = AXES_POS[0];
	const axis_rit = axis_lft + PLOT_CELLS[0];

	const canvas = document.getElementById("canvas");
	const context = canvas.getContext("2d");

	// Get paint ranges of columns
	let cell_size_y = PLOT_CELLS[1] / (yrange[1] - yrange[0] );
	let conv_d2g = x => axis_btm - (x - yrange[0] ) * cell_size_y;
	let xyz_gdata = new Array(3);
	xyz_gdata[0] = new Array(PLOT_CELLS[0] );
	xyz_gdata[1] = new Array(PLOT_CELLS[0] );
	xyz_gdata[2] = new Array(PLOT_CELLS[0] );
	const g_btm_limit = Math.ceil(conv_d2g(yrange[0] ) );
	const g_top_limit = Math.ceil(conv_d2g(yrange[1] ) );	// top coodinate is smaller than bottom
	for (let i=0; i<3; i++) {	// for X, Y and Z
		if (!WF_AXES[i] ) continue;

		let gdata = xyz_gdata[i];
		let index = index0;
		for (let j=0; j<PLOT_CELLS[0]; j++) {	// for every graph columns
			// calculate min/max of summarize unit
			let v_min, v_max;
			for (let k=0; k<n_sum; k++) {
				let value = wf_xyz[i][index];
				index++;
				if (k == 0) {
					v_min = value;
					v_max = value;
				} else {
					v_min = Math.min(v_min, value);
					v_max = Math.max(v_max, value);
				}
			}
			let g_coord_y_h = Math.max(g_top_limit, Math.min(g_btm_limit, Math.ceil(conv_d2g(v_max) ) ) );
			let g_coord_y_l = Math.max(g_top_limit, Math.min(g_btm_limit, Math.ceil(conv_d2g(v_min) ) ) );
			gdata[j] = [g_coord_y_l,g_coord_y_h];
			if (j > 0) {
				if (gdata[j-1][1] > g_coord_y_l) {			// previous top is lower on the screen than current lower point
					let mid = (gdata[j-1][1] + g_coord_y_l) / 2;
					let org_prev=gdata[j-1][1], org_cur=gdata[j][0];
					gdata[j-1][1] = Math.round(mid);
					gdata[j][0] =   Math.round(mid);
				} else if (gdata[j-1][0] < g_coord_y_h) {	// previous btm is higher on the screen than current higher point
					let mid = (gdata[j-1][0] + g_coord_y_h) / 2;
					let org_prev=gdata[j-1][0], org_cur=gdata[j][1];
					gdata[j-1][0] = Math.round(mid);
					gdata[j][1] =   Math.round(mid);
					// console.log(i+","+j+", previous btm higher on the screen than current top: "+org_prev+" -> "+gdata[j-1][1]
									// +", cur_top: "+org_cur+" -> "+gdata[j][0]+", mid="+mid);
				}
			}
		}
	}

	// Clear Camvas
	context.clearRect(0, 0, canvas.width, canvas.height);
	context.font = '12px sans-serif';
	context.textAlign = "start";
	context.textBaseline = 'alphabetic';

	// Draw frame on canvas
	context.fillStyle = 'ivory';
	context.strokeStyle = 'rgba(160,160,160,1.0)';
	context.fillRect(0, 0, canvas.width, canvas.height);
	context.strokeRect(0, 0, canvas.width, canvas.height);
	context.beginPath();
	context.lineWidth = 2;
	context.rect(axis_lft, axis_top, PLOT_CELLS[0], PLOT_CELLS[1]);
	context.closePath();
	context.stroke();

	// Unit of the Value
	const leg_pads = [5,2,5,5];	// left,top,right,between items
	const leg_height = 12;
	context.fillStyle = 'rgba(0,0,0,1.0)';
	if (data_souce == "Velocity") {
		document.getElementById("display_unit1").innerHTML = "[mm/s]";
		context.fillText("[mm/s]",leg_pads[0],3*leg_height); 
	} else {
		document.getElementById("display_unit1").innerHTML = "[m/s<sup>2</sup>]";
		context.fillText("[m/s²]", leg_pads[0], 3*leg_height); 
	}
	// Unit of time
	context.textAlign = "end";
	context.fillText("[s]",axis_rit,axis_btm+2*leg_height); 

	// Draw grid -- Horizontal + Tick Lables
	const gdiv_y = PLOT_CELLS[1] / ydiv[1];
	const ddiv_y = ydiv[0];
	let cur_y = axis_top;
	let cur_val = yrange[1];
	const decimal = cur_val >= 100 ? 0: (cur_val >= 10 ? 1: 2);
	context.textAlign = "end";
	let tick_offset = 0;
	while (cur_y <= axis_btm) {
		if (cur_y > axis_top && cur_y < axis_btm) {
			context.beginPath();
			context.lineWidth = 1;
			context.strokeStyle = 'rgba(216,216,216,1.0)';
			context.moveTo(axis_lft-1,cur_y);
			context.lineTo(axis_rit,cur_y);
			context.closePath();
			context.stroke();
		}
		context.fillText(cur_val.toFixed(decimal), axis_lft - leg_pads[0], cur_y + 8*leg_height/20 + tick_offset); 
		cur_y += gdiv_y;
		if ((axis_btm - cur_y) < 5) tick_offset = -5;
		cur_val -= ddiv_y;
	}
	// Vertical + Tick Lables
	const gdiv_x = pxdiv;
	const ddiv_x = xdiv;	// points/div / odr
	const dx = ddiv_x / gdiv_x;
	let cur_x;
	if (index0 > 0) {
		cur_val = ddiv_x * Math.ceil(xrange[0] / ddiv_x);
		cur_x = axis_lft + Math.round((cur_val - xrange[0] ) / dx);
	} else {
		cur_val = xrange[0];
		cur_x = axis_lft;
	}
	context.textAlign = "center";
	let x_tick_cnt = 0;
	while (cur_x <= axis_rit) {
		if (gdiv_x > 40 || (x_tick_cnt % 2) == 0 ) {
			if (cur_x > axis_lft && cur_x < axis_rit) {
				context.beginPath();
				context.lineWidth = 1;
				context.strokeStyle = 'rgba(216,216,216,1.0)';
				context.moveTo(cur_x,axis_btm-1);
				context.lineTo(cur_x,axis_top);
				context.closePath();
				context.stroke();
			}
			if (cur_val < 1700) {
				context.fillText(cur_val.toFixed(2), cur_x, axis_btm + leg_height); 
			}
		}
		x_tick_cnt++;
		cur_x += gdiv_x;
		cur_val += ddiv_x;
	}

	// Draw graphs
	let adj,len;
	for (let i=0; i<3; i++) {
		if (!WF_AXES[i] ) continue;

		gdata = xyz_gdata[i];
		let color = XYZ_COLORS[i];
		for (let j=0; j<PLOT_CELLS[0]; j++) {
			context.beginPath();
			context.lineWidth = 1;
			context.strokeStyle = color;

			len = gdata[j][1] - gdata[j][0];
			adj = len == 0 ? -2: (len == -1 ? -1: (len == 1 ? 1 :0) );
			context.moveTo(axis_lft+j,gdata[j][0] );
			context.lineTo(axis_lft+j,gdata[j][1]+adj);

			context.closePath();
			context.stroke();
		}
	}
}

function blackman_window(N) {
	// w(n) = 0.42 - 0.5 cos(2pi n/M) + 0.08 cos(4pi n/M)
	let dp1 = 2 * Math.PI / N;
	let dp2 = 4 * Math.PI / N;
	let win = new Array(N);
	let win_sum = 0.0;

	for (let i=0; i<N; i++) {
		win[i] = 0.42 - 0.5 * Math.cos(i * dp1) + 0.08 * Math.cos(i * dp2);
		win_sum += win[i];
	}
	return [win,win_sum];
}

let pk_rows=[],all_rows=[];
let mag_min,mag_max,mag_ave,left_index,dfreq,plot_pks=12,plot_minmax=false;
let fft_type_txt;

function remove_all_results_table() {
	table = document.getElementById("results");
	for (let i=0,r; r=all_rows[i]; i++) table.removeChild(r);
	all_rows = [];
}

function show_all_results_table() {
	if (type_is_wf() && wf_xyz) {
		mag_min = wf_xyz[0];
		mag_ave = wf_xyz[1];
		mag_max = wf_xyz[2];
	}

	if (!(mag_min && mag_ave && mag_max) ) return;

	table = document.getElementById("results");
	// remove existing rows
	remove_all_results_table();

	// add new rows
	let x_delta =  type_is_wf() ? 1 / 3200 / c_freq: dfreq;
	let x_num =    type_is_wf() ? 32000: chunk_len / 2;
	let x_digits = type_is_wf() ? 7: 3;
	for (let i=0; i<x_num; i++) {
		let tr =  document.createElement("tr");
		tr.innerHTML = `<td colspan="2">${(i*x_delta).toFixed(x_digits) }</td>`
					 + `<td>${mag_min[i].toFixed(3) }</td>`
					 + `<td>${mag_ave[i].toFixed(3) }</td>`
					 + `<td>${mag_max[i].toFixed(3) }</td>`;
		table.appendChild(tr);
		all_rows.push(tr);
	}
}

const FFT_COLOR = 'rgba(31,119,180,1.0)';	// tab:blue	  #1f77b4
const X_COLOR = 'rgba(31,119,180,0.7)';	// tab:blue	  #1f77b4
const Y_COLOR = 'rgba(255,127,14,0.7)';	// tab:orange #ff7f0e
const Z_COLOR = 'rgba(44,160,44,0.7)';	// tab:green  #2ca02c
const XYZ_COLORS = [X_COLOR, Y_COLOR, Z_COLOR];
function show_params_on_graph(fft_times=0, fft_size=0,show_xyz=false) {
	const canvas = document.getElementById("canvas");
	const context = canvas.getContext("2d");
	context.textBaseline = 'top';
	context.textAlign = "left";
	context.font = '16px sans-serif';
	let txt_fill = 'rgba(0,0,0,0.6)'
	let bg_fill = 'rgba(255,255,255,0.6)';
	let bg_h = 25;

	function show_text_w_bg(txt,x,y) {
		let bg_w = context.measureText(txt).width + 6;
		let bg_x = x - 3;
		let bg_y = y - 3;
		context.fillStyle = bg_fill;
		context.fillRect(bg_x,bg_y,bg_w,bg_h);
		context.fillStyle = txt_fill;
		context.fillText(txt,x,y);
	}
	show_text_w_bg(fft_type_txt, AXES_POS[0]+15, 15);
	show_text_w_bg(dldata_FN.trim()+"@"+dldata_ID.trim(),AXES_POS[0]+175,15);
	if (document.getElementById('data_src').value == 'LF_A') {
		context.font = 'bold 16px sans-serif';
		show_text_w_bg('LF Accel.',AXES_POS[0]+15,40);
		let lpf_fc = document.getElementById('lpf_fc').value;
		let num_fc = Number(lpf_fc);
		if (type_is_wf() && num_fc > 0) {
			if (num_fc == 10000) {
				lpf_fc = "AC";
			} else if (num_fc > 10000) {
				lpf_fc = "AC"+(num_fc - 10000).toFixed();
			}
			show_text_w_bg('LPF('+lpf_fc+')',AXES_POS[0]+100,40);
		}
	}
	if (fft_times) {
		context.font = 'bold 10px sans-serif';
		show_text_w_bg('FFT (size='+fft_size+')',AXES_POS[0]+513,15);
		show_text_w_bg('x'+fft_times+' time'+(fft_times>1 ? 's': ''),AXES_POS[0]+540,30);
	} else if (show_xyz) {
		context.font = 'bold 20px sans-serif';
		if (WF_AXES[0] ) {
			context.fillStyle = X_COLOR;
			context.fillText("X", AXES_POS[0]+540, 15);
		}
		if (WF_AXES[1] ) {
			context.fillStyle = Y_COLOR;
			context.fillText("Y", AXES_POS[0]+555, 15);
		}
		if (WF_AXES[2] ) {
			context.fillStyle = Z_COLOR;
			context.fillText("Z", AXES_POS[0]+570, 15);
		}
	}
}

// UI status flags
let PREV_LPF_WAS_AC = false;
let PREV_TYPE_WAS_WF = false;
let PREV_DATA_WAS_LF = false;


function enable_minmax() {
	document.getElementById("minmax_span").style.display = 'inline';
}

function disable_minmax() {
	document.getElementById("minmax_span").style.display = 'none';
}

function enable_lpf() {
	console.log("Enable_LPF");
	document.getElementById("lpf_span").style.display = "inline";

	const ele = document.getElementById("v_range");
	if (ele.options.length <= 10) {
		const option9 = document.createElement('option');
		const optionText9 = document.createTextNode('LPFAuto');
		option9.appendChild(optionText9);
		option9.setAttribute('value','LFA');
		ele.appendChild(option9);
	}
	if (Number(document.getElementById("lpf_fc").value) >= 10000)	// AC
		ele.options[10].selected = true;
}

function disable_lpf() {
	console.log("Disable_LPF");
	document.getElementById("lpf_span").style.display = "none";

	const ele = document.getElementById("v_range");
	if (ele.options.length < 11)
		return;
	ele.options[10].remove();
}

function update_range_minimax_lpf(from) {
	let cur_data = document.getElementById('data_src').value;
	let cur_fspan = document.getElementById('f_span').value;
	// console.log("update_range_minimax_lpf called from", from);
	// console.log("cur_data",cur_data,"; cur_fspan",cur_fspan,"; type_is_wf()",type_is_wf() );

	// LPF control
	if (type_is_wf() && cur_data == "LF_A") {
		enable_lpf();
	} else {
		disable_lpf();
	}

	// MinMax control
	if (!type_is_wf() && ["1.6k","960","480","240"].includes(cur_fspan) ) {
		enable_minmax();
	} else {
		disable_minmax();
	}
}

function switch_pks() {
	plot_pks = Number(document.getElementById("pltpks").value);
	if (plot_pks == 1) {
		document.getElementById("peak_s").style.display = 'none';
	} else {
		document.getElementById("peak_s").style.display = 'inline';
	}
	if (mag_min != undefined && mag_max != undefined && mag_ave != undefined && left_index != undefined && dfreq != undefined) {
		mk_graph(mag_ave,mag_min,mag_max,left_index,dfreq,plot_pks,plot_minmax);
		show_params_on_graph(fft_count, get_chunklen() );
		show_NSXe();
	}
	if (plot_pks) {
		document.getElementById("pks_num").innerHTML = ''+plot_pks+' Peak'+(plot_pks == 1 ? '': 's');
		document.getElementById("pks_hdr").style.display = 'table-cell';
		document.getElementById("pks_div").style.display = 'block';
		show_peaks();
	} else {
		document.getElementById("pks_hdr").style.display = 'none';
		document.getElementById("pks_div").style.display = 'none';
	}
}

function switch_minmax() {
	plot_minmax = document.getElementById("minmax").checked;
	if (mag_min != undefined && mag_max != undefined && mag_ave != undefined && left_index != undefined && dfreq != undefined) {
		mk_graph(mag_ave,mag_min,mag_max,left_index,dfreq,plot_pks,plot_minmax);
		show_params_on_graph(fft_count, get_chunklen() );
		show_NSXe();
	}
}

function on_lpf_fc() {
	if (!PREV_LPF_WAS_AC && Number(document.getElementById('lpf_fc').value) >= 10000) {
		PREV_LPF_WAS_AC = true;
		if (document.getElementById('v_range').options[0].selected)
			document.getElementById('v_range').options[10].selected = true;
	}
	if (PREV_LPF_WAS_AC && Number(document.getElementById('lpf_fc').value) < 10000) {
		PREV_LPF_WAS_AC = false;
	}
	test();
}

function type_is_wf() {
	return (
		document.getElementById("fft_type").value == "WFX" ||
		document.getElementById("fft_type").value == "WFY" ||
		document.getElementById("fft_type").value == "WFZ" ||
		document.getElementById("fft_type").value == "WF3"
	);
}

function get_n_chunk_sum() {
	let n_chnk;
	let n_sum;
	switch (document.getElementById("f_span").value) {
		case '960':	// 6s
			n_sum = 31;
			break;
		case '480':	// 3s
			n_sum = 16;
			break;
		case '240':	// 1.5s
			n_sum = 8;
			break;
		case '120':	// 0.75s
			n_sum = 4;
			break;
		case '60':	// 0.38s
			n_sum = 2;
			break;
		case '30':	// 0.19s
			n_sum = 1;
			break;
		default:	// 1.6k = 10s
			n_sum = 52;
			break;
	}
	n_chnk = PLOT_CELLS[0] * n_sum;
	return [n_chnk,n_sum];
}

function get_chunklen() {
	let chnk_len;
	switch (document.getElementById("f_span").value) {
		case '1.6k':
			chnk_len = 1024;
			break;
		case '960':
			chnk_len = 2048;
			break;
		case '240':
			chnk_len = 8192;
			break;
		case '120':
			chnk_len = 16384;
			break;
		case '60':
			chnk_len = 32768;
			break;
		case '30':
			chnk_len = 65536;
			break;
		case '15':
			chnk_len = 131072;
			break;
		default:	// 480
			chnk_len = 4096;
			break;
	}
	return chnk_len;
}

function calc_index0() {
	if (type_is_wf() ) {
		let n_s = get_n_chunk_sum()[1];		// number to summarize
		let px_len = 32000 / n_s;			// total plot pixel count
		let dt = 10 / c_freq / 32000;		// data sample interval
		let tstart = Number.parseFloat(document.getElementById("counter1").value);
		let idx0 = Math.round(tstart / dt);	// data index of start

		let max_idx0 = n_s * (px_len - PLOT_CELLS[0] );	// max allowable start index of data
		max_idx0 = max_idx0 < 0 ? 0: max_idx0;			// no negative value
		idx0 = idx0 > max_idx0 ? max_idx0: idx0;		// adjust index of data
		let max_tstart = Number.parseFloat((dt * max_idx0).toFixed(2) );

		document.getElementById("counter1").value = (dt * idx0).toFixed(2);
		document.getElementById("counter1").max = max_tstart;
		return [idx0,max_tstart];
	} else {
		let c_len = get_chunklen();			// FFT chunk lengthComputable
		let df = c_freq * 3200 / c_len;		// FFT resolution
		let fstart = Number(document.getElementById("counter1").value);
		let idx0 = Math.round(fstart/df);	// start index of FFT result to plot

		let max_idx0 = c_len / 2 - PLOT_CELLS[0];	// c_len / 2 is the FFT result length
		max_idx0 = max_idx0 < 0 ? 0: max_idx0;		// max allowable start index of FFT result
		idx0 = idx0 > max_idx0 ? max_idx0: idx0;	// adjust index of FFT result
		let max_fstart = Math.round(df * max_idx0);

		document.getElementById("counter1").value = Math.round(df * idx0);
		document.getElementById("counter1").max = max_fstart;
		return [idx0,max_fstart];
	}
}

function show_peaks() {
	if (peaks && plot_pks) {
		let num = Math.min(plot_pks,peaks.length);
		let idx0 = calc_index0()[0];
		document.getElementById("pks_num").innerHTML = ''+num+' Peak'+(num == 1 ? '': 's');
		document.getElementById("pks_hdr").style.display = 'table-cell';
		document.getElementById("pks_div").style.display = 'block';
		let table = document.getElementById("peaks");
		// remove existing rows
		for (let i=0,r; r=pk_rows[i]; i++) table.removeChild(r);
		// add new rows
		pk_rows = [];
		for (let i=0; i<num; i++) {
			let tr =  document.createElement("tr");
			tr.innerHTML = `<td>${i + 1}</td>`
						 + `<td colspan="2">${(dfreq*(peaks[i][1]+idx0) ).toFixed(3) }</td>`
						 + `<td>${(mag_min[peaks[i][1]+idx0] ).toFixed(3) }</td>`
						 + `<td>${(mag_ave[peaks[i][1]+idx0] ).toFixed(3) }</td>`
						 + `<td>${(mag_max[peaks[i][1]+idx0] ).toFixed(3) }</td>`;
			table.appendChild(tr);
			pk_rows.push(tr);
		}
	} else {
		document.getElementById("pks_hdr").style.display = 'none';
		document.getElementById("pks_div").style.display = 'none';
	}
}

function test() {
	const canvas = document.getElementById("canvas");
	const context = canvas.getContext("2d");
	let w_middle = canvas.width / 2, h_middle = canvas.height / 2;
	// Clear Camvas
	context.clearRect(0, 0, canvas.width, canvas.height);

	// Draw message
	context.fillStyle = 'rgba(100,0,150,0.3)';
	context.textBaseline = 'middle';
	context.textAlign = "center";
	context.font = '72px sans-serif';
	context.fillText("Busy...",w_middle,h_middle-30);
	context.font = '24px sans-serif';
	context.fillText("Please wait while graph is being prepared.",w_middle,h_middle+30);

	// Remove blink attribute
	document.getElementById("ds_prompt").classList.remove('blink');

	// Allow brower to draw the message
	setTimeout(test_sub, 5);
}

function test_sub() {
	// Select Data source
	raw_xyz = data_souce == "Accel" ? acc_xyz: (data_souce == "Velocity" ? vel_xyz: lf_xyz);
	document.getElementById("display_unit1").innerHTML = data_souce == "Velocity" ? "[mm/s]": "[m/s<sup>2</sup>]";
	document.getElementById("display_unit2").innerHTML = data_souce == "Velocity" ? "[mm/s]": "[m/s<sup>2</sup>]";

	// Waveform
	if (type_is_wf() ) {
		wf_sub();
		return;
	}

	// Select FFT type
	let fft_type_selctn = document.getElementById("fft_type").value;
	fft_type_txt = "FFT(";
	switch (fft_type_selctn) {
		case 'Y':
			env_xyz = raw_xyz[1];
			fft_type_txt = fft_type_txt + "RawWF[Y])"
			break;
		case 'Z':
			env_xyz = raw_xyz[2];
			fft_type_txt = fft_type_txt + "RawWF[Z])"
			break;
		case 'XE':
			env_xyz = new Float64Array(32000);
			calc_env_xyz(raw_xyz[0],null,null,env_xyz);
			fft_type_txt = fft_type_txt + "Envelope[X])"
			break;
		case 'YE':
			env_xyz = new Float64Array(32000);
			calc_env_xyz(raw_xyz[1],null,null,env_xyz);
			fft_type_txt = fft_type_txt + "Envelope[Y])"
			break;
		case 'ZE':
			env_xyz = new Float64Array(32000);
			calc_env_xyz(raw_xyz[2],null,null,env_xyz);
			fft_type_txt = fft_type_txt + "Envelope[Z])"
			break;
		case '3D':
			env_xyz = new Float64Array(32000);
			calc_env_xyz(raw_xyz[0],raw_xyz[1],raw_xyz[2],env_xyz);
			fft_type_txt = fft_type_txt + "Envelope[3D])"
			break;
		default:	// X
			env_xyz = raw_xyz[0];
			fft_type_txt = fft_type_txt + "RawWF[X])"
			break;
	}
	// Select F.Span
	chunk_len = get_chunklen();

	let halfN = chunk_len / 2;
	let win = null;
	let win_sum = null;
	let y_real = new Array(chunk_len);
	let y_imag = new Array(chunk_len);
	let f_real = null;
	let f_imag = null;
	let mag2min = new Array(halfN);
	let mag2max = new Array(halfN);
	let mag2sum = new Array(halfN);

	mag_min = new Array(halfN);
	mag_max = new Array(halfN);
	mag_ave = new Array(halfN);
	dfreq = c_freq * 3200 / chunk_len;
	left_index = calc_index0()[0];

	let start_index = 0;
	fft_count = 0;

	if (chunk_len >= 32000) {
		// Window
		[win,win_sum] = blackman_window(32000);

		// calculate mean (DC component)
		let mean = 0;
		for(let i = 0; i < 32000 ; i++ ){
			// mean += va3d[start_index+i];
			mean += env_xyz[start_index+i];
		}
		mean /= 32000;

		// apply Window
		for(let i = 0; i < chunk_len ; i++ ){
			y_real[i] = i < 32000 ? win[i] * (env_xyz[start_index+i] - mean): 0;
			y_imag[i] = 0.;
		}

		// FFT
		[f_real,f_imag] = fft(y_real,y_imag,chunk_len);

		// Absolute
		let mag2;
		for (let i=0; i<(chunk_len/2); i++) {
			mag2 = Math.pow(f_real[i],2) + Math.pow(f_imag[i],2);
			mag2min[i] = mag2;
			mag2max[i] = mag2;
			mag2sum[i] = mag2;
		}

		fft_count = 1;
	} else {
		// Window
		[win,win_sum] = blackman_window(chunk_len);

		while ((start_index + chunk_len) <= env_xyz.length) {
			// calculate mean (DC component)
			let mean = 0;
			for(let i = 0; i < chunk_len ; i++ ){
				// mean += va3d[start_index+i];
				mean += env_xyz[start_index+i];
			}
			mean /= chunk_len;

			// apply Window
			for(let i = 0; i < chunk_len ; i++ ){
				// y_real[i] = win[i] * (va3d[start_index+i] - mean);
				y_real[i] = win[i] * (env_xyz[start_index+i] - mean);
				y_imag[i] = 0.;
			}

			// FFT
			[f_real,f_imag] = fft(y_real,y_imag,chunk_len);

			// Absolute
			let mag2;
			for (let i=0; i<(chunk_len/2); i++) {
				mag2 = Math.pow(f_real[i],2) + Math.pow(f_imag[i],2);
				if (fft_count == 0) {
					mag2min[i] = mag2;
					mag2max[i] = mag2;
					mag2sum[i] = mag2;
				} else {
					mag2min[i] = Math.min(mag2min[i],mag2);
					mag2max[i] = Math.max(mag2max[i],mag2);
					mag2sum[i] += mag2;
				}
			}

			fft_count++;
			start_index += halfN;
		}
	}

	// Scaling
	let win_scale = 2.0 / win_sum;	// apply 2x for taking the negative frequency portion into account

	let win_scale_sum = win_scale / Math.sqrt(fft_count);
	for (let i=0; i<(chunk_len/2); i++) {
		mag_min[i] = win_scale * Math.sqrt(mag2min[i] );
		mag_max[i] = win_scale * Math.sqrt(mag2max[i] );
		mag_ave[i] = win_scale_sum * Math.sqrt(mag2sum[i] );
	}
	
	mag_min[0] *= 0.5;	// cancel 2x correction for DC
	mag_max[0] *= 0.5;	// cancel 2x correction for DC
	mag_ave[0] *= 0.5;	// cancel 2x correction for DC

	// prepare peaks
	let idx0 = calc_index0()[0];
	get_peaks(mag_ave,idx0,dfreq);

	// show graph
	mk_graph(mag_ave,mag_min,mag_max,left_index,dfreq,plot_pks,plot_minmax);

	// FFT Type
	show_params_on_graph(fft_count, chunk_len);

	// show peaks
	show_peaks();

	// show raw results
	if (document.getElementById("show_all").value == 'show') {
		show_all_results_table();
	}

	// Peak detection range
	let f_uppdr = dfreq*pk_excl_idx[1];
	document.getElementById("fpk1").innerHTML = (dfreq*pk_excl_idx[0] ).toFixed(3);
	document.getElementById("fpk2").innerHTML = (f_uppdr < 1600 ? f_uppdr: 1600).toFixed(3);

	show_NSXe();
}


function wf_sub() {
	// show graph
	left_index = calc_index0()[0];
	dfreq = get_n_chunk_sum()[1];	// Summary number
	wf_graph(left_index,dfreq);

	// Graph Type
	let el = document.getElementById('fft_type');
	fft_type_txt = el.options[el.selectedIndex].text;
	show_params_on_graph(0, 0, true);

	show_NSXe();
}

function read_conanair() {
	// Don't download conanair if this was launched from local file.
	if (document.URL.toLowerCase().startsWith('file') ) {
		alert("You can't download conanair measurement directly, because you opened this page from local file.");
		return;
	}
	// Clear file dialog value and file info
	document.getElementById("infile").value = "";
	document.getElementById("file_info").innerHTML = "";
	read_sample_file(
		function(x){
			get_point_def(x);
		},
		null
	);
}

function app_start() {
	// file api
	check_file_APIs();
	// Apple?
	if (
		[
		'iPad Simulator',
		'iPhone Simulator',
		'iPod Simulator',
		'iPad',
		'iPhone',
		'iPod'
	  ].includes(navigator.platform)
	  // iPad on iOS 13 detection
	  || (navigator.userAgent.includes("Mac") && "ontouchend" in document)
	) {
		document.getElementById("file_dialog").style.display = "none";
		document.getElementById("dnd_msg").style.display = "none";
	}
	// Don't read conanair if this was launched from local file.
	if (document.URL.toLowerCase().startsWith('file') ) {
		document.getElementById("last_measurement").style.display = "none";
	}

	// opening message
	const canvas = document.getElementById("canvas");
	const context = canvas.getContext("2d");
	let w_middle = canvas.width / 2, h_middle = canvas.height / 2;
	context.fillStyle = 'rgba(0,0,0,0.3)';
	context.textBaseline = 'middle';
	context.textAlign = "center";
	context.font = '72px sans-serif';
	context.fillText("FFT@conanair",w_middle,h_middle-30);
	context.font = '24px sans-serif';
	context.fillText("Please select dataset.",w_middle,h_middle+30);

	// Don't read conanair if this was launched from local file.
	// if (! document.URL.toLowerCase().startsWith('file') ) {
		// read_conanair();
	// } else {
		// document.getElementById("last_measurement").style.display = "none";
	// }

	show_NSXe();
}

function update_graph() {
	if (raw_xyz) {
		document.getElementById("update").disabled = true;
		test();
	}
}

let WF_AXES = [false,false,false];
function on_fft_type() {
	let ele;

	// disable data "LF Accel." if type is either of Envelope types
	let cur_type = document.getElementById('fft_type').value;
	if (["XE", "YE", "ZE", "3D"].includes(cur_type) ) {
		document.getElementById('data_src').options[2].disabled = true;
	} else {	// enable data "LF Accel." otherwise
		document.getElementById('data_src').options[2].disabled = false;
	}

	WF_AXES = [false,false,false];
	if (type_is_wf() ) {
		switch (document.getElementById("fft_type").value) {
			case 'WFX':
				WF_AXES = [true,false,false];
				break;
			case 'WFY':
				WF_AXES = [false,true,false];
				break;
			case 'WFZ':
				WF_AXES = [false,false,true];
				break;
			case 'WF3':
				WF_AXES = [true,true,true];
				break;
			default:
				WF_AXES = [false,false,false];
				break;
		}
		if (!PREV_TYPE_WAS_WF) {
			document.getElementById("counter1").value = "0";
			document.getElementById("counter1").step = "0.01";
			document.getElementById("start_unit").innerHTML = "s&nbsp;";

			document.getElementById("lab_pks").style.color = "gray";
			document.getElementById("pltpks").disabled = true;

			let texts = ["All","6 s","3 s","1.5 s","0.75 s","0.38 s","0.19 s"];
			for (let idx=0; idx<texts.length; idx++) {
				document.getElementById("f_span").options[idx].innerHTML = texts[idx];
			}
			document.getElementById("f_span").options[0].selected = true;

			document.getElementById("f_span").remove(7);
			document.getElementById("f_span").options[0].selected = true;

			texts = ["±0.5","±1","±2","±5","±10","±20","±50","±100","±200"];
			for (let idx=0; idx<texts.length; idx++) {
				document.getElementById("v_range").options[idx+1].innerHTML = texts[idx];
			}

			// enable_lpf();

			document.getElementById("pks_hdr").style.display = 'none';
			document.getElementById("pks_div").style.display = 'none';

			document.getElementById("show_all").value = 'hide';
			switch_show_all();
			
			document.getElementById("all_result").style.display = 'none';
			document.getElementById("allres_hdr_1_1").innerHTML = "Time<br/>[s]";
			document.getElementById("allres_hdr_1_2").innerHTML = "Amplitude ";
			texts = ["X","Y","Z"];
			for (let idx=0; idx<texts.length; idx++) {
				document.getElementById("allres_hdr_2").children[idx].innerHTML = texts[idx];
			}
		}

		manage_f_start();
		update_range_minimax_lpf("on_fft_type_1");

		PREV_TYPE_WAS_WF = true;

	} else if (PREV_TYPE_WAS_WF)  {
		// console.log("Waveform ->FFT, options.length =",document.getElementById("v_range").options.length);
		document.getElementById("counter1").value = "0";
		document.getElementById("counter1").step = "1";
		document.getElementById("start_unit").innerHTML = "Hz";

		document.getElementById("lab_pks").style.color = "black";
		document.getElementById("pltpks").disabled = false;

		let texts = ["1.6 kHz","960 Hz","480 Hz","240 Hz","120 Hz","60 Hz","30 Hz"];
		for (let idx=0; idx<texts.length; idx++) {
			document.getElementById("f_span").options[idx].innerHTML = texts[idx];
		}
		let cur_src = document.getElementById('data_src').value;
		let default_span = cur_src == "LF_A" ? 3: 2;
		document.getElementById("f_span").options[default_span].selected = true;

		const option7 = document.createElement('option');
		const optionText9 = document.createTextNode('15 Hz');
		option7.appendChild(optionText9);
		option7.setAttribute('value','15');
		document.getElementById("f_span").appendChild(option7);

		texts = ["0.5","1","2","5","10","20","50","100","200"];
		for (let idx=0; idx<texts.length; idx++) {
			document.getElementById("v_range").options[idx+1].innerHTML = texts[idx];
		}

		ele = document.getElementById("v_range");
		ele.options[0].selected = true;

		if (raw_xyz) {
			document.getElementById("pks_hdr").style.display = 'block';
			document.getElementById("pks_div").style.display = 'block';
		}

		document.getElementById("show_all").value = 'hide';
		switch_show_all();

		document.getElementById("allres_hdr_1_1").innerHTML = "Frequency<br/>[Hz]";
		document.getElementById("allres_hdr_1_2").innerHTML = "Intensity ";

		texts = ["Min.","Ave.","Max"];
		for (let idx=0; idx<texts.length; idx++) {
			document.getElementById("allres_hdr_2").children[idx].innerHTML = texts[idx];
		}

		manage_f_start();
		update_range_minimax_lpf("on_fft_type_2");

		PREV_TYPE_WAS_WF = false;
	}
	update_graph();
}

function manage_f_start() {
	// manage start spinner
	let span = document.getElementById("f_span").value;
	if (span == "1.6k") {
		document.getElementById("counter1").value = 0;
		document.getElementById("counter1").disabled = true;
		document.getElementById("counter1_up").disabled = true;
		document.getElementById("counter1_dn").disabled = true;
	} else {
		let new_max;
		if (type_is_wf() ) { // Waveform
			new_max = 10 - Number(span);
			if (c_freq) new_max = calc_index0()[1];
		} else {	// FFT
			new_max = 1600 - Number(span);
			if (c_freq) new_max = calc_index0()[1];
		}

		if (new_max >= 0 && Number(document.getElementById("counter1").value) > new_max) {
			// console.log(new_max);
			document.getElementById("counter1").value = new_max;
		}
		document.getElementById("counter1").max = new_max;
		document.getElementById("counter1").disabled = false;
		document.getElementById("counter1_up").disabled = false;
		document.getElementById("counter1_dn").disabled = false;
	}
}

function on_f_span() {
	let span = document.getElementById("f_span").value;
	if ((span != "1.6k" && Number(span) < 240) || type_is_wf() ) {
		document.getElementById("minmax_span").style.display = 'none';
		if (!type_is_wf() && Number(span) < 120 && document.getElementById("show_all").value == 'show') {
			switch_show_all();
		}
	} else {
		document.getElementById("minmax_span").style.display = 'inline';
	}
	manage_f_start();
	update_range_minimax_lpf("on_f_span");
	update_graph();
}

function on_v_range() {
	update_graph();
}

function switch_show_all() {
	let all_res = document.getElementById("all_result");
	let wait_res = document.getElementById("wait_result");
	let show_all_input = document.getElementById("show_all");
	let show_all_state = show_all_input.value;
	let span = document.getElementById("f_span").value;
	if (show_all_state == 'show') {
		const msg1 = "You will get huge data (";
		const msg2 = " rows) in All results section.\n" +
					"This may keep you waiting quite a while and might crash your browser.\n\n" +
					"Do you really want to see them?\n" +
					'(You can hide "All results" by choosing cancel)';
		if (!type_is_wf() && span != "1.6k" && Number(span) < 120) {
			let rows_str = (8192 * Math.pow(2,60/Number(span) ) ).toLocaleString();
			if (! window.confirm(msg1 + rows_str + msg2)) {
				show_all_state = 'hide';
				show_all_input.value = show_all_state;
			}
		} else if(type_is_wf() ) {
			let rows_str = (32000).toLocaleString();
			if (! window.confirm(msg1 + rows_str + msg2)) {
				show_all_state = 'hide';
				show_all_input.value = show_all_state;
			}
		}
	}
	if (show_all_state == 'show') {
		wait_res.style.visibility = 'visible';
		setTimeout(function(){
			show_all_results_table();
			all_res.style.display = 'block';
			wait_res.style.visibility = 'hidden';
		}, 5);
	} else {
		wait_res.style.visibility = 'visible';
		setTimeout(function(){
			remove_all_results_table();
			all_res.style.display = 'none';
			wait_res.style.visibility = 'hidden';
		}, 5);
	}
}

function switch_show_summary() {
	let summary = document.getElementById("summary");
	let summary_input = document.getElementById("show_summary");
	let summary_state = summary_input.value;
	if (summary_state == 'show') {
		if (summary_ok) {
			document.getElementById("acc_pk").innerHTML =	sum_acc_pk_x.toFixed(3) + "<br/>"
															+ sum_acc_pk_y.toFixed(3) + "<br/>"
															+ sum_acc_pk_z.toFixed(3) + "<br/>"
															+ sum_acc_pk_3d.toFixed(3);
			document.getElementById("acc_rms").innerHTML =	sum_acc_rms_x.toFixed(3) + "<br/>"
															+ sum_acc_rms_y.toFixed(3) + "<br/>"
															+ sum_acc_rms_z.toFixed(3) + "<br/>"
															+ sum_acc_rms_3d.toFixed(3);
			document.getElementById("vel_pk").innerHTML =	sum_vel_pk_x.toFixed(3) + "<br/>"
															+ sum_vel_pk_y.toFixed(3) + "<br/>"
															+ sum_vel_pk_z.toFixed(3) + "<br/>"
															+ sum_vel_pk_3d.toFixed(3);
			document.getElementById("vel_rms").innerHTML =	sum_vel_rms_x.toFixed(3) + "<br/>"
															+ sum_vel_rms_y.toFixed(3) + "<br/>"
															+ sum_vel_rms_z.toFixed(3) + "<br/>"
															+ sum_vel_rms_3d.toFixed(3);
		} else {
			document.getElementById("acc_pk").innerHTML =	"";
			document.getElementById("acc_rms").innerHTML =	"";
			document.getElementById("vel_pk").innerHTML =	"";
			document.getElementById("vel_rms").innerHTML =	"";
		}
		summary.style.display = 'block';
	} else {
		summary.style.display = 'none';
	}
}

function on_start_chng() {
	manage_f_start();

	if (raw_xyz) {
		document.getElementById("update").disabled = false;
		document.getElementById("update").focus();
	}
}

let arySpinnerCtrl = [];
let spin_speed = 50;
let spin_count = 0;

// on click, tap, long-press
function on_spin_bgn(e) {
	if(arySpinnerCtrl['interval'] ) return false;
	let target = document.getElementById('counter1');
	let delta = Number.parseFloat(target.step);
	arySpinnerCtrl['target'] = target;
	arySpinnerCtrl['timestamp'] = e.timeStamp;
	arySpinnerCtrl['cal'] = e.target.id == 'counter1_up' ? delta: -delta;

	// on click
	if(e.type == 'click'){
		spinner_updt();
		arySpinnerCtrl = [];
		spin_count = 0;
		return false;
	}

	// on long press
	setTimeout(function(){
		if(!arySpinnerCtrl['interval'] && arySpinnerCtrl['timestamp'] == e.timeStamp){
			arySpinnerCtrl['interval'] = setInterval(spinner_updt, spin_speed);
		}
	}, 500);
}

// on release, scroll
function on_spin_end(e) {
	if(arySpinnerCtrl['interval']){
		clearInterval(arySpinnerCtrl['interval']);
		arySpinnerCtrl = [];
		spin_count = 0;
	}
}

// change spinner
function spinner_updt(){
	let spin_multi;
	spin_count++;
	if (spin_count > 30) {
		spin_multi = 8;
		spin_count = 31;	// prevent overflow
	} else if (spin_count > 20) {
		spin_multi = 4;
	} else if (spin_count > 10) {
		spin_multi = 2;
	} else {
		spin_multi = 1;
	}
	let target = arySpinnerCtrl['target'];
	let num = Number.parseFloat(target.value);
	num += spin_multi * arySpinnerCtrl['cal'];
	num = Number.parseFloat(num.toFixed(arySpinnerCtrl['cal']==1 || arySpinnerCtrl['cal']==-1 ? 0: 2) );
	if(num > Number.parseFloat(target.max) ){
		target.value = target.max;
	}else if(Number.parseFloat(target.min) > num){
		target.value = target.min;
	}else{
		if (arySpinnerCtrl['cal'] == 1 || arySpinnerCtrl['cal'] == -1) {
			target.value = num.toFixed();
		} else {
			target.value = num.toFixed(2);
		}
		if (raw_xyz) document.getElementById("update").disabled = false;
	}
}

document.getElementById('counter1_up').addEventListener('touchstart', on_spin_bgn, false);
document.getElementById('counter1_up').addEventListener('mousedown', on_spin_bgn, false);
document.getElementById('counter1_up').addEventListener('click', on_spin_bgn, false);
document.getElementById('counter1_dn').addEventListener('touchstart', on_spin_bgn, false);
document.getElementById('counter1_dn').addEventListener('mousedown', on_spin_bgn, false);
document.getElementById('counter1_dn').addEventListener('click', on_spin_bgn, false);

document.getElementById('counter1_up').addEventListener('touchend', on_spin_end, false);
document.getElementById('counter1_up').addEventListener('mouseup', on_spin_end, false);
document.getElementById('counter1_up').addEventListener('scroll', on_spin_end, false);
document.getElementById('counter1_dn').addEventListener('touchend', on_spin_end, false);
document.getElementById('counter1_dn').addEventListener('mouseup', on_spin_end, false);
document.getElementById('counter1_dn').addEventListener('scroll', on_spin_end, false);
