Parcourir la source

Improve graph display

- Set a suggested min & max so smaller ranges (eg: 0.5 - 1.2) don't look like
  large differences.
- Use a larger window time for longer time periods so fewer points are displayed.
  - Use a much smaller window (10 minutes) for the gauges, to ensure they're
    showing a more up-to-date value.
- Include date code at midnight timestamps so it's easier to see days on graphs.
- Default to 12 hours.
Jason Tarka il y a 4 ans
Parent
commit
0b72835f56

+ 7 - 1
web-view/src/js/consts.ts

@@ -1,7 +1,13 @@
 export const HOURS_QUERY:string = 'hours';
-export const HOURS_DEFAULT:number = 2;
+export const HOURS_DEFAULT:number = 12;
 export const MAX_HOURS_AGO = 168;
 
+/**
+ * The total padding to add to a range on a graph to make tiny differences
+ * smoother. Half is added to the top, half to the bottom.
+ */
+export const RANGE_PADDING:number = 5;
+
 export type FieldInfo = {
 	key: string,
 	label: string,

+ 18 - 2
web-view/src/js/graphs.ts

@@ -1,6 +1,6 @@
 import { Chart, registerables } from 'chart.js';
 import {TimeSeries, Values} from './types';
-import {FieldInfo, FIELDS} from './consts';
+import {FieldInfo, FIELDS, RANGE_PADDING} from './consts';
 import {formatTime} from './utils';
 Chart.register(...registerables);
 
@@ -55,6 +55,15 @@ function createGraph(
 	canvas:HTMLCanvasElement,
 	fieldInfo:FieldInfo
 ) {
+	let minMax = data.reduce((prev, curr) => {
+		return isNaN(curr.value) ?
+			prev
+			: {
+				min: Math.min(prev.min, curr.value),
+				max: Math.max(prev.max, curr.value)
+			};
+	}, {min: Infinity, max: -Infinity});
+
 	let config:any = {
 		type: 'line',
 		data: {
@@ -66,7 +75,14 @@ function createGraph(
 				borderColor: fieldInfo.colour // Colour of the line
 			}]
 		},
-		options: {}
+		options: {
+			scales: {
+				y: {
+					suggestedMin: minMax.min - (RANGE_PADDING / 2),
+					suggestedMax: minMax.max + (RANGE_PADDING / 2)
+				}
+			}
+		}
 	};
 	new Chart(canvas, config);
 }

+ 17 - 5
web-view/src/js/main.ts

@@ -7,6 +7,7 @@ import {Reading} from './types';
 import {createGauges, createGraphs} from './graphs';
 import {processData} from './processData';
 import {HOUR_OPTIONS, HOURS_DEFAULT, HOURS_QUERY, MAX_HOURS_AGO} from './consts';
+import {getWindowTime} from './utils';
 
 const app = initializeApp(firebaseConfig);
 const rtdb = getDatabase(app);
@@ -14,16 +15,27 @@ const rtdb = getDatabase(app);
 document.body.onload = init;
 
 async function init() {
-	const readings = await getRecentData(getHoursAgo());
-	const values = processData(readings);
-
+	const hoursAgo = getHoursAgo();
+	const readings = await getRecentData(hoursAgo);
+
+	// Force a 15-minute window for the gauges, to ensure it shows the most
+	// up-to-date value it can (that's had spikes filtered out)
+	const gaugeValues = processData(
+		readings.slice(-10),
+		{ alignWindow: 10 }
+	);
 	createGauges(
-		values,
+		gaugeValues,
 		document.getElementById('gauges')
 	);
 
+	// Have graphs use a potentially longer window time, so they don't show as
+	// many elements when a longer time is selected.
+	const graphValues = processData(readings, {
+		alignWindow: getWindowTime(hoursAgo)
+	});
 	createGraphs(
-		values,
+		graphValues,
 		document.getElementById('time-series-graphs')
 	);
 

+ 5 - 5
web-view/src/js/processData.ts

@@ -4,7 +4,7 @@ import {FIELD_KEYS, FIELDS} from './consts';
 
 export type ProcessingConfig = {
 	/** The number of minutes to group on. */
-	alignWindow: number,
+	alignWindow?: number,
 
 	/**
 	 * The minimum absolute difference between elements before
@@ -12,7 +12,7 @@ export type ProcessingConfig = {
 	 * earlier diff of 0.01, but isn't a spike. A `minChange` of
 	 * 2 would ignore this.
 	 */
-	minSpikeChange: number,
+	minSpikeChange?: number,
 
 	/**
 	 * The minimum multiplier of differences between sibling
@@ -21,7 +21,7 @@ export type ProcessingConfig = {
 	 * is a 2.5x difference. A min multiplier of 3 would
 	 * ignore this.
 	 */
-	minSpikeMultiplier: number
+	minSpikeMultiplier?: number
 };
 
 const defaultConfig:ProcessingConfig = {
@@ -39,12 +39,12 @@ const defaultConfig:ProcessingConfig = {
  */
 export function processData(
 	data:Reading[],
-	config?:ProcessingConfig
+	config:ProcessingConfig = defaultConfig
 ):Values {
 	// Note: This is not tested, as everything it calls is tested, and setting up
 	// an expected output would be very cumbersome.
 
-	config = config || defaultConfig;
+	config = Object.assign(defaultConfig, config);
 
 	const values:Values = splitData(data);
 

+ 28 - 0
web-view/src/js/utils.test.ts

@@ -0,0 +1,28 @@
+import should = require('should');
+import {formatTime} from './utils';
+
+describe('Utils', () => {
+	describe('Format Time', () => {
+		[
+			{
+				input: new Date(2022, 1, 2, 9, 43),
+				expected: '09:43'
+			}, {
+				input: new Date(2022, 1, 2, 15, 0),
+				expected: '15:00'
+			}, {
+				input: 1641183875023,
+				expected: '23:24' // Assumes EST timezone
+			}, {
+				input: new Date(2022, 1, 2, 0, 0),
+				expected: '2022-01-02 00:00'
+			}, {
+				input: new Date(2022, 10, 12, 0, 0),
+				expected: '2022-10-12 00:00'
+			},
+		].forEach(data => it(`Formats ${data.input} to ${data.expected}`, () => {
+			const actual = formatTime(data.input);
+			should(actual).equal(data.expected);
+		}));
+	});
+});

+ 13 - 1
web-view/src/js/utils.ts

@@ -1,5 +1,7 @@
 /**
  * Get the time (in localtime) for the date, padded properly with zeros.
+ * At 00:00, include the date.
+ *
  * @param date
  */
 export function formatTime(date:Date|number):string {
@@ -7,8 +9,18 @@ export function formatTime(date:Date|number):string {
 		date = new Date(date);
 	}
 
-	return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
+	let time = `${pad(date.getHours())}:${pad(date.getMinutes())}`;
+	return time === '00:00'
+		? `${date.getFullYear()}-${pad(date.getMonth())}-${pad(date.getDate())} ${time}`
+		: time;
+
 	function pad(num:number):string {
 		return num < 10 ? `0${num}` : num.toString();
 	}
 }
+
+export function getWindowTime(totalHours:number) {
+	if(totalHours <= 6) return 15;
+	if(totalHours <= 24) return 30;
+	return 60;
+}