blob: ff5d602458f33ef267e08fa233d355be776bb624 [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* HeatMapEventBuffer buffer the heatmap event.
* @constructor
* @param {int} maxSize max size of the buffer.
*/
function HeatMapEventBuffer(maxSize = 50) {
this.maxSize = maxSize;
this.rawData = [];
this.avgData = [];
this.floorLevel = 0;
this.sigData = [];
this.sigData3 = [];
}
/**
* Following are set of helper functions used to calculate avg, sig, and floor.
*/
// Elementwise sum of 2d array.
ElementwiseSum = arr=>arr.reduce((arr1,arr2)=>arr1.map((a,i)=>a+arr2[i]));
// Average of 2d array.
Average2D = arr=>ElementwiseSum(arr).map(a=>a/arr.length);
// Max of 1d array.
Max1D = arr=>arr.reduce((a,b)=>Math.max(a,b));
// Average of 1d Array.
Average1D = arr=>arr.reduce((a,b)=>a+b)/arr.length
/**
* Function to add new event to the buffer.
* @param {object} newEventData new heatmap data
*/
HeatMapEventBuffer.prototype.appendEvent = function(newEventData) {
if (this.rawData.length >= this.maxSize) {
this.rawData.shift();
}
if (this.avgData.length >= this.maxSize) {
this.avgData.shift();
}
if (this.sigData.length >= this.maxSize) {
this.sigData.shift();
}
this.rawData[this.rawData.length] = newEventData;
this.avgData = Average2D(this.rawData);
variance = Average2D(this.rawData.map(arr=>arr.map(
(a, i)=>Math.pow(a - this.avgData[i], 2))));
this.sigData = variance.map(x=>Math.sqrt(x));
this.sigData3 = this.sigData.map((a,i)=>3*a + this.avgData[i]);
this.floorLevel = Average1D(this.rawData.map(arr=>Max1D(arr)));
}
/**
* Function to get dataTable from select columns. The dataTable will be used to
* draw the linechart.
* @return {google.visualization.DataTable}
*/
HeatMapEventBuffer.prototype.getDataTable = function() {
var data = new google.visualization.DataTable();
data.addColumn('number', 'value');
data.addColumn('number', 'Floor');
data.addColumn('number', 'Sigma');
data.addColumn('number', 'Raw');
data.addColumn('number', 'Avg');
rows = [];
lastIndex = this.rawData.length - 1;
if (!this.rawData.length) {
return data;
}
for (var i = 0; i < this.rawData[lastIndex].length; i++) {
rows[rows.length] = [i, this.floorLevel, this.sigData3[i],
this.rawData[lastIndex][i], this.avgData[i]]
}
data.addRows(rows);
return data;
}
/**
* EventBufferWrapper wrapper for x and y buffer.
* @constructor
*/
function EventBufferWrapper() {
this.xBuffer = new HeatMapEventBuffer();
this.yBuffer = new HeatMapEventBuffer();
this.startRecordFlag = false;
this.serilizedHeatMap = "";
}
/**
* Function to add new event to the correct buffer.
* @param {object} newEvent new heatmap event
*/
EventBufferWrapper.prototype.appendEvent = function(newEvent) {
if (newEvent.sensor == 'x') {
this.xBuffer.appendEvent(newEvent.data);
} else if (newEvent.sensor == 'y') {
this.yBuffer.appendEvent(newEvent.data);
}
if (this.startRecordFlag) {
this.serilizedHeatMap += newEvent.sensor;
this.serilizedHeatMap += ",";
for (var i = 0; i < newEvent.data.length; i++) {
this.serilizedHeatMap += newEvent.data[i];
this.serilizedHeatMap += ",";
}
this.serilizedHeatMap += "\n";
}
}
/**
* Function to get dataTable for the selected sensor.
* @param {string} sensor x or y.
*/
EventBufferWrapper.prototype.getDataTable = function(sensor) {
if (sensor == 'x') {
return this.xBuffer.getDataTable();
} else if (sensor == 'y') {
return this.yBuffer.getDataTable();
}
}
/**
* Function to start recording heatmap.
* @param {string} sensor x or y.
*/
EventBufferWrapper.prototype.startRecord = function() {
this.startRecordFlag = true;
this.serilizedHeatMap = "flag,";
for (var i = 0; i < 64; i ++) {
this.serilizedHeatMap += i;
this.serilizedHeatMap += ",";
}
this.serilizedHeatMap += "\n";
}
/**
* Function to stop recording heatmap and save the log file.
* @param {string} sensor x or y.
*/
EventBufferWrapper.prototype.stopRecordAndSave = function() {
this.startRecordFlag = false;
var element = document.createElement('a');
element.setAttribute('href',
'data:text/plain;charset=utf-8,'
+ encodeURIComponent(this.serilizedHeatMap));
element.setAttribute('download', "heatmap.csv");
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
/**
* Draw chart for HeatMap readings.
* @constructor
* @param {Element} xChartDiv the div to draw x chart.
* @param {Element} yChartDiv the div to draw y chart.
*/
function HeatMapChart(xChartDiv, yChartDiv) {
this.eventBuffer = new EventBufferWrapper();
this.initCharts(xChartDiv, yChartDiv);
this.drawCharts();
}
/**
* Get chart options.
* @param {string} vName Name of the v axis.
*/
HeatMapChart.prototype.getChartOptions = function(vName) {
var options = {
vAxis: {
title: vName,
minValue: 1000,
maxValue: 66000,
},
connectSteps: false,
// The order of series is: Floor, Sigma, Raw, Average
colors:['pink','yellow', '#000066', '#99ff33'],
series: {
0: {areaOpacity: 0.3},
1: {areaOpacity: 1},
2: {areaOpacity: 1},
3: {areaOpacity: 0}},
};
return options;
}
/**
* This function init all the line charts.
* @param {Element} xChartDiv div for the x chart.
* @param {Element} yChartDiv div for the y chart.
*/
HeatMapChart.prototype.initCharts = function(xChartDiv, yChartDiv) {
this.xChart = new google.visualization.SteppedAreaChart(xChartDiv);
this.yChart = new google.visualization.SteppedAreaChart(yChartDiv);
}
/**
* This function draw all the chart of the data from eventBuffer.
*/
HeatMapChart.prototype.drawCharts = function() {
this.xChart.draw(this.eventBuffer.getDataTable('x'),
this.getChartOptions('x'));
this.yChart.draw(this.eventBuffer.getDataTable('y'),
this.getChartOptions('y'));
}
/**
* Process an incoming HeatMapEvent.
* @param {object} event
*/
HeatMapChart.prototype.processHeatMapEvent = function(event) {
this.eventBuffer.appendEvent(event);
}
/**
* Send setPole to backend and select the pole.
* @param {object} event
*/
function setPole(pole) {
window.ws.send('setPole:' + pole);
window.console.log('setPole:' + pole);
heatMapChart.eventBuffer = new EventBufferWrapper();
}
/**
* Create a web socket and a new TouchLineChart object.
*/
function createWS() {
var websocket = document.getElementById('websocketUrl').innerText;
if (window.WebSocket) {
ws = new WebSocket(websocket);
ws.addEventListener("message", function(event) {
var heatMapEvent = JSON.parse(event.data);
heatMapChart.processHeatMapEvent(heatMapEvent);
});
} else {
alert('WebSocket is not supported on this browser!')
}
heatMapChart = new HeatMapChart(document.getElementById('xChart'),
document.getElementById('yChart'));
window.setInterval(function() {
heatMapChart.drawCharts();
}, 33);
}
/**
* Init google chart.
*/
function InitChart() {
google.charts.load('current', {'packages':['corechart']});
google.charts.setOnLoadCallback(createWS);
}