|
|
|
var page_waveform = (function () {
|
|
|
|
var wfm = {};
|
|
|
|
|
|
|
|
var zoomResetFn;
|
|
|
|
var dataFormat;
|
|
|
|
|
|
|
|
var readoutPending = false;
|
|
|
|
var autoReload = false;
|
|
|
|
var autoReloadTime = 1;
|
|
|
|
var arTimeout = -1;
|
|
|
|
|
|
|
|
var lastLoadMs;
|
|
|
|
|
|
|
|
var zoomSavedX, zoomSavedY;
|
|
|
|
|
|
|
|
var readXhr; // read xhr
|
|
|
|
|
|
|
|
var lastObj = null; // samples are stored here
|
|
|
|
|
|
|
|
var opts = {
|
|
|
|
count: 0, // sample count
|
|
|
|
freq: 0 // sampling freq
|
|
|
|
};
|
|
|
|
|
|
|
|
function buildChart(j) {
|
|
|
|
// Build the chart
|
|
|
|
var mql = window.matchMedia('screen and (min-width: 544px)');
|
|
|
|
var isPhone = !mql.matches;
|
|
|
|
|
|
|
|
var fft = (j.stats.format == 'FFT');
|
|
|
|
|
|
|
|
var xLabel, yLabel;
|
|
|
|
if (fft) {
|
|
|
|
xLabel = 'Frequency - [ Hz ]';
|
|
|
|
yLabel = 'Magnitude - [ mA ]';
|
|
|
|
} else {
|
|
|
|
xLabel = 'Sample time - [ ms ]';
|
|
|
|
yLabel = 'Current - [ mA ]';
|
|
|
|
}
|
|
|
|
|
|
|
|
var peak = Math.max(-j.stats.min, j.stats.max);
|
|
|
|
var displayPeak = Math.max(peak, 10);
|
|
|
|
|
|
|
|
// Sidebar
|
|
|
|
|
|
|
|
$('#stat-count').html(j.stats.count);
|
|
|
|
$('#stat-f-s').html(numfmt(j.stats.freq, 2));
|
|
|
|
$('#stat-i-peak').html(numfmt(peak, 2));
|
|
|
|
$('#stat-i-rms').html(numfmt(j.stats.rms, 2));
|
|
|
|
$('.stats').removeClass('invis');
|
|
|
|
|
|
|
|
// --- chart ---
|
|
|
|
|
|
|
|
// Generate point entries
|
|
|
|
// add synthetic properties
|
|
|
|
var step = fft ? (j.stats.freq/j.stats.count) : (1000/j.stats.freq);
|
|
|
|
var points = _.map(j.samples, function (a, i) {
|
|
|
|
return {
|
|
|
|
x: i * step,
|
|
|
|
y: a
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
var plugins = [
|
|
|
|
Chartist.plugins.zoom({
|
|
|
|
resetOnRightMouseBtn: true,
|
|
|
|
onZoom: function (chart, reset) {
|
|
|
|
zoomResetFn = reset;
|
|
|
|
|
|
|
|
zoomSavedX = chart.options.axisX.highLow;
|
|
|
|
zoomSavedY = chart.options.axisY.highLow;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
];
|
|
|
|
|
|
|
|
if (!isPhone) plugins.push( // larger than phone
|
|
|
|
Chartist.plugins.ctAxisTitle({
|
|
|
|
axisX: {
|
|
|
|
axisTitle: xLabel,
|
|
|
|
offset: {
|
|
|
|
x: 0,
|
|
|
|
y: 55
|
|
|
|
}
|
|
|
|
},
|
|
|
|
axisY: {
|
|
|
|
axisTitle: yLabel,
|
|
|
|
flipText: true,
|
|
|
|
offset: {
|
|
|
|
x: 0,
|
|
|
|
y: 15
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
var xHigh, xLow, yHigh, yLow;
|
|
|
|
|
|
|
|
if (zoomSavedX) {
|
|
|
|
// we have saved coords of the zoom rect, restore the zoom.
|
|
|
|
xHigh = zoomSavedX.high;
|
|
|
|
xLow = zoomSavedX.low;
|
|
|
|
yHigh = zoomSavedY.high;
|
|
|
|
yLow = zoomSavedY.low;
|
|
|
|
} else {
|
|
|
|
yHigh = fft ? undefined : displayPeak;
|
|
|
|
yLow = fft ? 0 : -displayPeak;
|
|
|
|
}
|
|
|
|
|
|
|
|
new Chartist.Line('#chart', {
|
|
|
|
series: [
|
|
|
|
{
|
|
|
|
name: 'a',
|
|
|
|
data: points
|
|
|
|
},
|
|
|
|
]
|
|
|
|
}, {
|
|
|
|
showPoint: false,
|
|
|
|
showArea: fft,
|
|
|
|
fullWidth: true,
|
|
|
|
chartPadding: (isPhone ? {right: 20, bottom: 5, left: 0} : {right: 25, bottom: 30, left: 25}),
|
|
|
|
series: {
|
|
|
|
'a': {
|
|
|
|
lineSmooth: Chartist.Interpolation.monotoneCubic()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
axisX: {
|
|
|
|
type: Chartist.AutoScaleAxis,
|
|
|
|
//onlyInteger: !fft // only for raw
|
|
|
|
high: xHigh,
|
|
|
|
low: xLow,
|
|
|
|
},
|
|
|
|
axisY: {
|
|
|
|
type: Chartist.AutoScaleAxis,
|
|
|
|
//onlyInteger: true
|
|
|
|
high: yHigh,
|
|
|
|
low: yLow,
|
|
|
|
},
|
|
|
|
explicitBounds: {
|
|
|
|
xLow: 0,
|
|
|
|
yLow: fft ? 0 : undefined,
|
|
|
|
xHigh: points[points.length-1].x
|
|
|
|
},
|
|
|
|
plugins: plugins
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function onRxData(resp, status) {
|
|
|
|
readoutPending = false;
|
|
|
|
|
|
|
|
if (status != 200) {
|
|
|
|
errorMsg("Request failed.", 1000);
|
|
|
|
} else {
|
|
|
|
var j = JSON.parse(resp);
|
|
|
|
if (!j.success) {
|
|
|
|
errorMsg("Sampling failed.", 1000);
|
|
|
|
} else {
|
|
|
|
$('.chartexport').removeClass('hidden');
|
|
|
|
lastObj = j;
|
|
|
|
buildChart(j);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (autoReload)
|
|
|
|
arTimeout = setTimeout(requestReload, Math.max(0, autoReloadTime - msElapsed(lastLoadMs)));
|
|
|
|
}
|
|
|
|
|
|
|
|
function readInputs() {
|
|
|
|
opts.count = $('#count').val();
|
|
|
|
opts.freq = $('#freq').val() * (dataFormat == 'fft' ? 2 : 1); // bw 2x -> f_s
|
|
|
|
}
|
|
|
|
|
|
|
|
function requestReload() {
|
|
|
|
if (readoutPending) {
|
|
|
|
errorMsg("Request already pending - aborting.");
|
|
|
|
readXhr.abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
readoutPending = true;
|
|
|
|
lastLoadMs = msNow();
|
|
|
|
|
|
|
|
var n = opts.count;
|
|
|
|
var fs = opts.freq;
|
|
|
|
var url = _root+'/measure/'+dataFormat+'?n='+n+'&fs='+fs;
|
|
|
|
readXhr = $().get(url, onRxData, estimateLoadTime(fs,n));
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function toggleAutoReload() {
|
|
|
|
autoReloadTime = +$('#ar-time').val(); // ms
|
|
|
|
|
|
|
|
readInputs();
|
|
|
|
|
|
|
|
autoReload = !autoReload;
|
|
|
|
if (autoReload) {
|
|
|
|
requestReload();
|
|
|
|
} else {
|
|
|
|
clearTimeout(arTimeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
$('#ar-btn')
|
|
|
|
.toggleClass('btn-blue')
|
|
|
|
.toggleClass('btn-red')
|
|
|
|
.val(autoReload ? 'Stop' : 'Auto');
|
|
|
|
}
|
|
|
|
|
|
|
|
wfm.init = function (format) {
|
|
|
|
// --- Load data ---
|
|
|
|
dataFormat = format;
|
|
|
|
|
|
|
|
function onLoadClick() {
|
|
|
|
readInputs();
|
|
|
|
requestReload();
|
|
|
|
}
|
|
|
|
|
|
|
|
$('#load').on('click', onLoadClick);
|
|
|
|
|
|
|
|
$('.chartexport a').on('click', function() {
|
|
|
|
var sep = $(this).data('sep');
|
|
|
|
var str = '';
|
|
|
|
|
|
|
|
// for fft
|
|
|
|
var fftStep = (lastObj.stats.freq/lastObj.stats.count);
|
|
|
|
|
|
|
|
switch (sep) {
|
|
|
|
case 'space': str = lastObj.samples.join(' '); break;
|
|
|
|
case 'comma': str = lastObj.samples.join(','); break;
|
|
|
|
case 'newline': str = lastObj.samples.join('\n'); break;
|
|
|
|
|
|
|
|
case 'fft-csv':
|
|
|
|
str = _.map(lastObj.samples, function (a, i) {
|
|
|
|
return numfmt(i * fftStep,3) + "," + a;
|
|
|
|
}).join('\n');
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'fft-json':
|
|
|
|
str = JSON.stringify(_.map(lastObj.samples, function (a, i) {
|
|
|
|
return {f: +numfmt(i * fftStep, 3), m: a}
|
|
|
|
}));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!copyToClipboard(str)) {
|
|
|
|
var $cb = $('#copybox');
|
|
|
|
$cb.removeClass('hidden');
|
|
|
|
$cb.val(str);
|
|
|
|
} else {
|
|
|
|
infoMsg('Copy success!');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
$('#count,#freq').on('keyup', function (e) {
|
|
|
|
if (e.which == 13) {
|
|
|
|
onLoadClick();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// --- zooming ---
|
|
|
|
|
|
|
|
$('#chart').on('contextmenu', function (e) { // right click on the chart -> reset
|
|
|
|
zoomResetFn && zoomResetFn();
|
|
|
|
zoomResetFn = null;
|
|
|
|
|
|
|
|
zoomSavedX = null;
|
|
|
|
zoomSavedY = null;
|
|
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
|
|
// auto-reload button
|
|
|
|
$('#ar-btn').on('click', toggleAutoReload);
|
|
|
|
};
|
|
|
|
|
|
|
|
return wfm;
|
|
|
|
})();
|