commit e7b05877c5db56a6a651fbe083ca0d138b99ce12 Author: Henri Bergius Date: Tue Nov 28 19:05:49 2017 +0100 Initial experiments with plotly and openmct telemetry diff --git a/screens/heatmap.html b/screens/heatmap.html new file mode 100644 index 0000000..9727aab --- /dev/null +++ b/screens/heatmap.html @@ -0,0 +1,19 @@ + + + + + Heatmap + + + + + +
+ + + diff --git a/screens/heatmap.js b/screens/heatmap.js new file mode 100644 index 0000000..8bc4845 --- /dev/null +++ b/screens/heatmap.js @@ -0,0 +1,210 @@ +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'); + }); diff --git a/screens/polar.html b/screens/polar.html new file mode 100644 index 0000000..80ccd40 --- /dev/null +++ b/screens/polar.html @@ -0,0 +1,19 @@ + + + + + Polar scatter + + + + + +
+ + + diff --git a/screens/polar.js b/screens/polar.js new file mode 100644 index 0000000..0c24b4f --- /dev/null +++ b/screens/polar.js @@ -0,0 +1,178 @@ +const endDate = new Date(); +const startDate = new Date(); +startDate.setDate(startDate.getDate() - 90); +startDate.setHours(0, 0, 0); +const days = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24)); +const timeSlots = 24; +const timeSeries = 'bar.open'; +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 = []; + 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) { + const slots = prepareSlots(null); + const slotsSeen = []; + 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 (slotsSeen.indexOf(`${daySlot}_${timeSlot}`) !== -1) { + return; + } + if (slots[0][timeSlot] === null) { + slots[0][timeSlot] = 0; + } + slots[0][timeSlot] += pointValue; + if (pointValue > 0) { + slotsSeen.push(`${daySlot}_${timeSlot}`); + } + }); + + // Normalize to percentages + slots.forEach((dayData, dayIdx) => { + dayData.forEach((val, idx) => { + if (dayData[idx] === null) { + // No data + dayData[idx] = 0; + return; + } + dayData[idx] = dayData[idx] / days * 100; + }); + }); + + return slots; +} + +let layout = { + orientation: 270, + direction: 'clockwise', + angularaxis: { + type: 'category', + tickcolor: '#204a87', + }, + radialaxis: { + ticksuffix: '%', + tickcolor: '#204a87', + }, + font: { + family: ['Source Code Pro', 'sans-serif'], + size: 16, + color: '#fff', + outlineColor: 'transparent', + }, + //paper_bgcolor: 'rgba(0, 0, 0, 0.98)', + paper_bgcolor: 'transparent', + width: window.innerHeight, + height: window.innerHeight, + showlegend: false, +}; +let options = { + staticPlot: true, +}; +const data = []; +const dayLabels = getDayLabels(); +let color = [120, 120, 220]; +prepareSlots().forEach((day, dayIdx) => { + color[0] -= 10; + color[1] -= 10; + color[2] -= 10; + data.push({ + r: day, + t: getTimeLabels(), + name: dayLabels[dayIdx], + type: 'area', + marker: { + color: '#f57900', + }, + showlegend: false, + }); +}); +Plotly.newPlot('chart', data, layout, options); + +fetch(source) + .then((data) => data.json()) + .then((data) => mapData(data)) + .then((values) => { + values.forEach((day, dayIdx) => { + day.forEach((val, idx) => { + data[dayIdx].r[idx] = val; + }); + console.log(data[dayIdx]); + }); + Plotly.newPlot('chart', data, layout, options); + }); diff --git a/screens/windrose.html b/screens/windrose.html new file mode 100644 index 0000000..9874796 --- /dev/null +++ b/screens/windrose.html @@ -0,0 +1,19 @@ + + + + + Wind rose + + + + + +
+ + + diff --git a/screens/windrose.js b/screens/windrose.js new file mode 100644 index 0000000..3aead07 --- /dev/null +++ b/screens/windrose.js @@ -0,0 +1,218 @@ +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; + }); + }); + + return slots; +} + +let layout = { + orientation: 270, + direction: 'clockwise', + angularaxis: { + type: 'category', + tickcolor: '#f57900', + }, + radialaxis: { + ticksuffix: '', + tickcolor: '#f57900', + }, + font: { + family: ['Source Code Pro', 'sans-serif'], + size: 16, + color: '#fff', + outlineColor: 'transparent', + }, + paper_bgcolor: 'transparent', + width: window.innerHeight, + height: window.innerHeight, +}; +let options = { + staticPlot: true, +}; +const data = []; +const dayLabels = getDayLabels(); +let color = [120, 120, 220]; +prepareSlots().forEach((day, dayIdx) => { + color[0] -= 10; + color[1] -= 10; + color[2] -= 10; + data.push({ + r: day, + t: getTimeLabels(), + name: dayLabels[dayIdx], + type: 'area', + marker: { + color: `rgb(${color[0]}, ${color[1]}, ${color[2]})`, + }, + opacity: 0.8, + showlegend: false, + }); +}); +Plotly.newPlot('chart', data, layout, options); + +fetch(source) + .then((data) => data.json()) + .then((data) => mapData(data, accumulatePoints)) + .then((values) => { + values.forEach((day, dayIdx) => { + day.forEach((val, idx) => { + //let modedVal = parseFloat(`${dayIdx}.${parseInt(val)}`); + data[dayIdx].r[idx] = val; + }); + }); + Plotly.newPlot('chart', data, layout, options); + //Plotly.redraw('chart'); + });