infoscreens/screens/heatmap.js

210 lines
5.1 KiB
JavaScript

const endDate = new Date();
const startDate = new Date();
startDate.setDate(startDate.getDate() - 7);
startDate.setHours(0, 0, 0);
const days = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24));
const timeSlots = 24;
const timeSeries = 'crew.online';
const accumulatePoints = false;
//const timeSeries = 'clima.pressure.txl';
//const accumulatePoints = false;
//const timeSeries = 'bar.open';
//const accumulatePoints = false;
const interpolate = true;
const usePreviousValue = false;
const source = `http://openmct.cbrp3.c-base.org/telemetry/${timeSeries}?start=${startDate.getTime()}&end=${endDate.getTime()}`;
const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
function prepareSlots(defaultValue = 0) {
const slots = [];
for (let i = 0; i < days; i++) {
daySlot = [];
for (let ii = 0; ii < timeSlots; ii++) {
daySlot.push(defaultValue);
}
slots.push(daySlot);
}
return slots;
}
prepareSlots();
function getDayLabels() {
const labels = [];
const day = new Date(startDate.getTime());
for (let i = 0; i < days; i++) {
labels.push(`${day.getDate()} ${weekDays[day.getDay()]}`);
day.setHours(day.getHours() + 24);
}
return labels;
}
function pad(time) {
let val = `${time}`;
if (val.length === 1) {
val = `0${time}`;
}
return val;
}
function getTimeLabels() {
const labels = [];
const day = new Date(startDate.getTime());
day.setHours(0, 0, 0);
for (let i = 0; i < timeSlots; i++) {
let from = day.getHours();
day.setHours(day.getHours() + (24 / timeSlots));
let to = day.getHours();
if (to === 0) {
to = 24;
}
if (timeSlots === 24) {
labels.push(`${pad(from)}`);
continue;
}
labels.push(`${pad(from)}-${pad(to)}`);
}
return labels;
}
function flattenData(data, fromDay = 0, fromHour = 0) {
const flattened = [];
data.forEach((dayData, dayIdx) => {
if (dayIdx < fromDay) {
return;
}
dayData.forEach((point, hourIdx) => {
if (dayIdx === fromDay && hourIdx <= fromHour) {
return;
}
flattened.push(point);
});
});
return flattened;
}
function parseValue(val) {
if (typeof val === 'boolean') {
if (val) {
return 1;
}
return 0;
}
return parseFloat(val);
}
function mapData(data, accumulate = false) {
const slots = prepareSlots(null);
data.forEach((point) => {
const pointDate = new Date(point.timestamp);
const pointValue = parseValue(point.value);
const daySlot = Math.floor((pointDate - startDate) / (1000 * 60 * 60 * 24));
const timeSlot = Math.floor(pointDate.getHours() / (24 / timeSlots))
if (slots[daySlot][timeSlot] === null) {
slots[daySlot][timeSlot] = 0;
}
if (accumulate) {
slots[daySlot][timeSlot] += pointValue;
} else {
slots[daySlot][timeSlot] = Math.max(pointValue, slots[daySlot][timeSlot]);
}
});
// Deal with slots without data
let prevVal = 0;
slots.forEach((dayData, dayIdx) => {
let prevValIdx = 0;
dayData.forEach((val, idx) => {
if (val !== null) {
prevVal = val;
prevValIdx = idx;
return;
}
if (interpolate) {
const flattened = flattenData(slots, dayIdx, idx);
for (let nextSlot = 0; nextSlot < flattened.length; nextSlot++) {
if (flattened[nextSlot] === null) {
continue;
}
let nextVal = flattened[nextSlot];
if (nextVal === prevVal) {
dayData[idx] = prevVal;
return;
}
let difference = 0;
if (nextVal > prevVal) {
difference = nextVal - prevVal;
} else {
difference = prevVal - nextVal;
}
let distance = nextSlot;
let totalDistance = nextSlot + idx - prevValIdx;
if (totalDistance < 1) {
totalDistance = 1;
}
dayData[idx] = Math.max((difference / totalDistance * distance) + nextVal, 0);
return;
}
dayData[idx] = 0;
return;
}
if (usePreviousValue) {
dayData[idx] = prevVal;
return;
}
dayData[idx] = 0;
});
});
console.log(slots);
return slots;
}
let layout = {
yaxis: {
autorange: 'reversed',
tickfont: {
family: 'Source Code Pro',
},
},
xaxis: {
type: 'category',
tickfont: {
family: 'Source Code Pro',
},
},
font: {
family: ['Source Code Pro', 'sans-serif'],
size: 16,
color: '#fff',
outlineColor: 'transparent',
},
paper_bgcolor: 'transparent',
};
let options = {
staticPlot: true,
};
let data = [
{
x: getTimeLabels(),
y: getDayLabels(),
z: prepareSlots(),
type: 'heatmap',
//colorscale: 'Bluered',
colorscale: [
['0.0', 'rgb(0, 0, 0)'],
['0.9', 'rgb(255, 0, 0)'],
['1.0', 'rgb(128, 0, 0)'],
],
showlegend: false,
showscale: false,
}
];
Plotly.newPlot('chart', data, layout, options);
fetch(source)
.then((data) => data.json())
.then((data) => mapData(data, accumulatePoints))
.then((values) => {
data[0].z = values;
Plotly.redraw('chart');
});