From 4282ae4470b51da8626f9417b29bb788eba3c7d4 Mon Sep 17 00:00:00 2001
From: Starbeamrainbowlabs <sbrl@starbeamrainbowlabs.com>
Date: Wed, 7 Aug 2019 16:58:06 +0100
Subject: [PATCH] [web interface] Add guage to the side

This is ported over from the air quality web interface.

Proof I wrote this is, of course, available upon request.
---
 client_src/css/index.css                  |  6 ++
 client_src/index.html                     |  2 +
 client_src/js/Guage.mjs                   | 93 +++++++++++++++++++++++
 client_src/js/Helpers/Canvas.mjs          | 37 +++++++++
 client_src/js/Helpers/GradientHelpers.mjs | 28 +++++++
 client_src/js/MapManager.mjs              | 13 ++++
 6 files changed, 179 insertions(+)
 create mode 100644 client_src/js/Guage.mjs
 create mode 100644 client_src/js/Helpers/Canvas.mjs
 create mode 100644 client_src/js/Helpers/GradientHelpers.mjs

diff --git a/client_src/css/index.css b/client_src/css/index.css
index c75d013..2f150c6 100644
--- a/client_src/css/index.css
+++ b/client_src/css/index.css
@@ -43,6 +43,12 @@ main {
 	z-index: 50;
 }
 
+#canvas-guage {
+    position: absolute;
+    bottom: 2em; right: 1em;
+    
+    z-index: 1000;
+}
 
 .working { cursor: progress !important; }
 
diff --git a/client_src/index.html b/client_src/index.html
index 89456e8..fbd1bf8 100644
--- a/client_src/index.html
+++ b/client_src/index.html
@@ -17,6 +17,8 @@
 			
 		</main>
 		
+		<canvas id="canvas-guage" width="50" height="300"></canvas>
+		
 		<!---------------->
 		<link rel="stylesheet" href="app.css" />
 		<script src="app.js" type="module" charset="utf-8"></script>
diff --git a/client_src/js/Guage.mjs b/client_src/js/Guage.mjs
new file mode 100644
index 0000000..9b39912
--- /dev/null
+++ b/client_src/js/Guage.mjs
@@ -0,0 +1,93 @@
+"use strict";
+
+import { set_hidpi_canvas, pixel_ratio } from './Helpers/Canvas.mjs';
+import { RenderGradient } from './Helpers/GradientHelpers.mjs';
+
+import { normalise_rssi } from '../../common/Normalisers.mjs';
+
+
+class Guage {
+	constructor(in_canvas) {
+		this.canvas = in_canvas;
+		
+		set_hidpi_canvas(this.canvas);
+		
+		this.context = this.canvas.getContext("2d");
+	}
+	
+	set_spec({ gradient: spec }) {
+		this.spec = spec;
+	}
+	
+	render() {
+		let guage_size = {
+			x: this.canvas.width * 0.6,
+			y: this.canvas.height * 0.05,
+			width: this.canvas.width * 0.3,
+			height: this.canvas.height * 0.9
+		};
+		
+		this.clear_canvas();
+		this.render_gauge(guage_size);
+		this.render_labels(guage_size);
+		
+	}
+	
+	clear_canvas() {
+		this.context.clearRect(
+			0, 0,
+			this.canvas.width, this.canvas.height
+		);
+	}
+	
+	render_gauge(guage_size) {
+		this.context.save();
+		
+		let gradient_spec = RenderGradient(this.spec);
+		// console.log(gradient_spec);
+		
+		let gradient = this.context.createLinearGradient(
+			0, guage_size.y + guage_size.height,
+			0, guage_size.y
+		);
+		for (let point in gradient_spec)
+			gradient.addColorStop(parseFloat(point), gradient_spec[point]);
+		
+		this.context.fillStyle = gradient;
+		this.context.fillRect(
+			guage_size.x, guage_size.y,
+			guage_size.width, guage_size.height
+		);
+		
+		this.context.restore();
+	}
+	
+	render_labels(guage_size) {
+		this.context.save();
+		
+		this.context.font = "12px Ubuntu, sans-serif";
+		this.context.textBaseline = "middle";
+		this.context.strokeStyle = "rgba(0, 0, 0, 0.5)";
+		this.context.lineWidth = 1.5 * pixel_ratio;
+		
+		for (let point in this.spec) {
+			let value = 1 - normalise_rssi(parseFloat(point));
+			
+			let text_width = this.context.measureText(point).width;
+			let draw_x = guage_size.x - 5 - text_width;
+			let draw_y = guage_size.y + (value * guage_size.height);
+			
+			// console.log(`Writing '${point}' to (${draw_x}, ${draw_y})`);
+			this.context.fillText(point, draw_x, draw_y);
+			
+			this.context.beginPath();
+			this.context.moveTo(guage_size.x + guage_size.width, draw_y);
+			this.context.lineTo(draw_x + text_width, draw_y);
+			this.context.stroke();
+		}
+		
+		this.context.restore();
+	}
+}
+
+export default Guage;
diff --git a/client_src/js/Helpers/Canvas.mjs b/client_src/js/Helpers/Canvas.mjs
new file mode 100644
index 0000000..9596568
--- /dev/null
+++ b/client_src/js/Helpers/Canvas.mjs
@@ -0,0 +1,37 @@
+"use strict";
+
+// From https://stackoverflow.com/a/15666143/1460422
+
+let pixel_ratio = (function () {
+    let ctx = document.createElement("canvas").getContext("2d"),
+        dpr = window.devicePixelRatio || 1,
+        bsr = ctx.webkitBackingStorePixelRatio ||
+              ctx.mozBackingStorePixelRatio ||
+              ctx.msBackingStorePixelRatio ||
+              ctx.oBackingStorePixelRatio ||
+              ctx.backingStorePixelRatio || 1;
+
+    return dpr / bsr;
+})();
+
+/**
+ * De-blurrificates a canvas on Hi-DPI screens.
+ * @param	{HTMLCanvasElement}	canvas					The canvas element to alter.
+ * @param	{Number}			width=canvas.width		Optional. The width of the canvas in question.
+ * @param	{Number}			height=canvas.height	Optional. The height of the canvas in question.
+ * @returns	{HTMLCanvasElement}	The canvas we operated on. Useful for daisy-chaining.
+ */
+function set_hidpi_canvas(canvas, width, height) {
+	width = width || canvas.width;
+	height = height || canvas.height;
+	
+    canvas.width = width * pixel_ratio;
+    canvas.height = height * pixel_ratio;
+    canvas.style.width = width + "px";
+    canvas.style.height = height + "px";
+    // canvas.getContext("2d").setTransform(pixel_ratio, 0, 0, pixel_ratio, 0, 0);
+    return canvas;
+}
+
+
+export { pixel_ratio, set_hidpi_canvas };
diff --git a/client_src/js/Helpers/GradientHelpers.mjs b/client_src/js/Helpers/GradientHelpers.mjs
new file mode 100644
index 0000000..236870c
--- /dev/null
+++ b/client_src/js/Helpers/GradientHelpers.mjs
@@ -0,0 +1,28 @@
+"use strict";
+
+import { normalise_rssi } from '../../../common/Normalisers.mjs';
+
+function RenderGradient(stops) {
+	let result = {};
+	
+	for(let value in stops)
+		result[normalise_rssi(value)] = stops[value];
+	
+	return result;
+}
+
+/**
+ * Generates a CSS gradient, given the output of RenderGradient().
+ * @param	{[string, string]}	stops	The stops specification to create a css linear-gradient from. Should be the output of RenderGradient().
+ * @returns	{string}	The rendered CSS linear-gradient.
+ */
+function GenerateCSSGradient(stops) {
+	let stops_processed = [];
+	for(let value in stops) {
+		let valueNumber = parseFloat(value);
+		stops_processed.push(`${stops[value]} ${(valueNumber*100).toFixed(3).replace(/\.?[0]+$/, "")}%`);
+	}
+	return `linear-gradient(to bottom, ${stops_processed.join(", ")})`;
+}
+
+export { RenderGradient, GenerateCSSGradient };
diff --git a/client_src/js/MapManager.mjs b/client_src/js/MapManager.mjs
index 6ef9d5f..ec6e4be 100644
--- a/client_src/js/MapManager.mjs
+++ b/client_src/js/MapManager.mjs
@@ -6,6 +6,7 @@ import L from 'leaflet';
 
 import LayerGateways from './LayerGateways.mjs';
 import LayerAI from './LayerAI.mjs';
+import Guage from './Guage.mjs';
 
 class MapManager {
 	constructor() {
@@ -30,12 +31,15 @@ class MapManager {
 		this.layer_gateways = new LayerGateways(this.map);
 		await this.layer_gateways.setup();
 		
+		this.setup_guage();
+		
 		// Add the AI coverage prediction layer
 		this.layer_ai = new LayerAI(this.map);
 		await this.layer_ai.setup();
 		
 		this.setup_layer_control();
 		
+		
 		document.querySelector("main").classList.remove("working", "working-visual");
 	}
 	
@@ -52,6 +56,15 @@ class MapManager {
 		});
 		this.layer_control.addTo(this.map);
 	}
+	
+	setup_guage() {
+		this.guage = new Guage(document.getElementById("canvas-guage"));
+		this.guage.set_spec({
+			gradient: Config.colour_scale,
+			max: -30
+		});
+		this.guage.render();
+	}
 }
 
 export default MapManager;
