[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.
This commit is contained in:
parent
eabc57ac51
commit
4282ae4470
6 changed files with 179 additions and 0 deletions
|
@ -43,6 +43,12 @@ main {
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#canvas-guage {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 2em; right: 1em;
|
||||||
|
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
.working { cursor: progress !important; }
|
.working { cursor: progress !important; }
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<canvas id="canvas-guage" width="50" height="300"></canvas>
|
||||||
|
|
||||||
<!---------------->
|
<!---------------->
|
||||||
<link rel="stylesheet" href="app.css" />
|
<link rel="stylesheet" href="app.css" />
|
||||||
<script src="app.js" type="module" charset="utf-8"></script>
|
<script src="app.js" type="module" charset="utf-8"></script>
|
||||||
|
|
93
client_src/js/Guage.mjs
Normal file
93
client_src/js/Guage.mjs
Normal file
|
@ -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;
|
37
client_src/js/Helpers/Canvas.mjs
Normal file
37
client_src/js/Helpers/Canvas.mjs
Normal file
|
@ -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 };
|
28
client_src/js/Helpers/GradientHelpers.mjs
Normal file
28
client_src/js/Helpers/GradientHelpers.mjs
Normal file
|
@ -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 };
|
|
@ -6,6 +6,7 @@ import L from 'leaflet';
|
||||||
|
|
||||||
import LayerGateways from './LayerGateways.mjs';
|
import LayerGateways from './LayerGateways.mjs';
|
||||||
import LayerAI from './LayerAI.mjs';
|
import LayerAI from './LayerAI.mjs';
|
||||||
|
import Guage from './Guage.mjs';
|
||||||
|
|
||||||
class MapManager {
|
class MapManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -30,12 +31,15 @@ class MapManager {
|
||||||
this.layer_gateways = new LayerGateways(this.map);
|
this.layer_gateways = new LayerGateways(this.map);
|
||||||
await this.layer_gateways.setup();
|
await this.layer_gateways.setup();
|
||||||
|
|
||||||
|
this.setup_guage();
|
||||||
|
|
||||||
// Add the AI coverage prediction layer
|
// Add the AI coverage prediction layer
|
||||||
this.layer_ai = new LayerAI(this.map);
|
this.layer_ai = new LayerAI(this.map);
|
||||||
await this.layer_ai.setup();
|
await this.layer_ai.setup();
|
||||||
|
|
||||||
this.setup_layer_control();
|
this.setup_layer_control();
|
||||||
|
|
||||||
|
|
||||||
document.querySelector("main").classList.remove("working", "working-visual");
|
document.querySelector("main").classList.remove("working", "working-visual");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +56,15 @@ class MapManager {
|
||||||
});
|
});
|
||||||
this.layer_control.addTo(this.map);
|
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;
|
export default MapManager;
|
||||||
|
|
Loading…
Reference in a new issue