From 61c8b76719f74ef00e169cfc45a6c62772abbb8a Mon Sep 17 00:00:00 2001
From: Starbeamrainbowlabs <sbrl@starbeamrainbowlabs.com>
Date: Wed, 25 May 2022 02:47:06 +0100
Subject: [PATCH] Use Plot.ly for simple gauges

---
 package-lock.json                             | 15 ++-
 package.json                                  |  3 +-
 src/static/app.css                            |  3 +
 src/static/js/tabledefs/cpu_live.mjs          |  2 +-
 src/static/js/ui/UIGauge.mjs                  | 92 +++++++++----------
 .../js/ui/charts/ChartStackedBarSet.mjs       | 17 +++-
 6 files changed, 79 insertions(+), 53 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 527e7b6..858e763 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,7 +27,8 @@
 				"emel": "^2.5.1",
 				"esbuild": "^0.14.38",
 				"fork-awesome": "^1.2.0",
-				"nightink": "^1.0.1"
+				"nightink": "^1.0.1",
+				"plotly.js-dist": "^2.12.1"
 			}
 		},
 		"node_modules/@emmetio/abbreviation": {
@@ -576,6 +577,12 @@
 				"node": ">=6"
 			}
 		},
+		"node_modules/plotly.js-dist": {
+			"version": "2.12.1",
+			"resolved": "https://registry.npmjs.org/plotly.js-dist/-/plotly.js-dist-2.12.1.tgz",
+			"integrity": "sha512-u3NumBhoNQw5fyr5sfK0jTIa4v1WwruAs0y5bSk055rKklFtyUgwwVZ8VEKCiWqfuPX7OPS3mE4PqvgP4mATOA==",
+			"dev": true
+		},
 		"node_modules/powahroot": {
 			"version": "1.2.6",
 			"resolved": "https://registry.npmjs.org/powahroot/-/powahroot-1.2.6.tgz",
@@ -960,6 +967,12 @@
 			"resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz",
 			"integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA=="
 		},
+		"plotly.js-dist": {
+			"version": "2.12.1",
+			"resolved": "https://registry.npmjs.org/plotly.js-dist/-/plotly.js-dist-2.12.1.tgz",
+			"integrity": "sha512-u3NumBhoNQw5fyr5sfK0jTIa4v1WwruAs0y5bSk055rKklFtyUgwwVZ8VEKCiWqfuPX7OPS3mE4PqvgP4mATOA==",
+			"dev": true
+		},
 		"powahroot": {
 			"version": "1.2.6",
 			"resolved": "https://registry.npmjs.org/powahroot/-/powahroot-1.2.6.tgz",
diff --git a/package.json b/package.json
index 123ba10..dacc253 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,8 @@
 		"emel": "^2.5.1",
 		"esbuild": "^0.14.38",
 		"fork-awesome": "^1.2.0",
-		"nightink": "^1.0.1"
+		"nightink": "^1.0.1",
+		"plotly.js-dist": "^2.12.1"
 	},
 	"___comment": "The browser field is used by esbuild. fs isn't a thing in the browser, ",
 	"browser": {
diff --git a/src/static/app.css b/src/static/app.css
index 6982700..e733282 100644
--- a/src/static/app.css
+++ b/src/static/app.css
@@ -159,6 +159,9 @@ th {
 .data-item > :not(:first-child) {
 	background: var(--tech-b);
 }
+.container-gauge {
+	min-width: 30em;
+}
 
 .peer-name {
 	display: flex;
diff --git a/src/static/js/tabledefs/cpu_live.mjs b/src/static/js/tabledefs/cpu_live.mjs
index 4e2801b..de3e3fe 100644
--- a/src/static/js/tabledefs/cpu_live.mjs
+++ b/src/static/js/tabledefs/cpu_live.mjs
@@ -6,7 +6,7 @@ export default {
 	items: [
 		{
 			name: "Frequency (GHz)",
-			type: "gauge",
+			type: "gauge_multi",
 			gauge: { min: 0, max: 6 },
 			content: {
 				"min": "frequency.min",
diff --git a/src/static/js/ui/UIGauge.mjs b/src/static/js/ui/UIGauge.mjs
index 4a97d78..4b04d76 100644
--- a/src/static/js/ui/UIGauge.mjs
+++ b/src/static/js/ui/UIGauge.mjs
@@ -2,15 +2,16 @@
 
 import Emel from 'emel';
 import forkawesome_emel from './forkawesome_emel.mjs';
-import ApexCharts from 'apexcharts';
+// import ChartStackedBarSet from './charts/ChartStackedBarSet.mjs';
+import Plotly from 'plotly.js-dist';
 
 import AbstractUIItem from './AbstractUIItem.mjs';
 
 import find_in_obj from '../misc/find_in_obj.mjs';
 
 class UIGauge extends AbstractUIItem {
-	get #chart_labels() { return this.chart_options.xaxis.categories; }
-	get #chart_data() { return this.chart_options.series[0].data; }
+	#chart_data = [];
+	#chart = null;
 	
 	constructor(el, def) {
 		super(el, def);
@@ -20,68 +21,66 @@ class UIGauge extends AbstractUIItem {
 			placeholders: [ this.def.name ]
 		}));
 		
-		this.chart_options = {
-			series: [{
-				name: def.name,
-				data: [] // TODO: Fill this with data values
-			}],
-			chart: {
-				type: "bar",
-				//height: 350,
-			},
-			plotOptions: { bar: {
-				horizontal: false,
-				
-			} },
-			xaxis: {
-				categories: [], // TODO: Fill this with hostname / ids
-			},
-			fill: { opacity: 1 },
-		}
+		this.el_gauge = this.el.querySelector(".container-gauge");
 		
-		this.chart = new ApexCharts(el.querySelector(".container-gauge"), this.chart_options);
 	}
 	
-	#__update_chart() {
-		this.chart.updateOptions(this.chart_options, true);
-		this.chart.render();
+	render() {
+		// Refuse to render an empty chart
+		if(this.#chart_data.length === 0) return;
+		
+		const data = {
+			x: [], // Labels
+			y: [], // Data numbers
+			type: "bar"
+		};
+		
+		for (const itemdef of this.#chart_data) {
+			let label_this = itemdef.label;
+			for (let i = 2; data.x.includes(label_this); i++)
+				label_this = `${itemdef.label} (${i})`;
+			data.x.push(label_this);
+			data.y.push(itemdef.data_item);
+		}
+		
+		console.log(`def ${this.def.name} plotly definition`, data);
+		
+		if(this.#chart === null) {
+			console.log(`def ${this.def.name} plotly NEW`);
+			this.#chart = Plotly.newPlot(this.el_gauge, [data]);
+		}
+		else {
+			console.log(`def ${this.def.name} plotly REACT`);
+			this.#chart = Plotly.react(this.el_gauge, [data]);
+		}
 	}
 	
 	#__insert_item(label, data_item) {
-		for(let i in this.chart_labels) {
-			if(this.#chart_labels[i] === label) {
-				this.#chart_labels[i] = label;
-				this.#chart_data[i] = data_item;
-				return;
-			}
-		}
-		
 		const comparer = new Intl.Collator(navigator.language);
 		
-		for(let i in this.#chart_labels) {
+		for(let i in this.#chart_data) {
 			const comp_name = comparer.compare(
 				label.toLowerCase(),
-				this.#chart_labels[i].toLowerCase()
+				this.#chart_data[i].label.toLowerCase()
 			);
 			
 			if(comp_name < 0) { // Insert immediately before this index
-				this.#chart_labels.splice(i, 0, label);
-				this.#chart_data.splice(i, 0, data_item);
+				this.#chart_data.splice(i, 0, {
+					label,
+					data_item
+				});
 				return;
 			}
 		}
-		this.#chart_labels.push(label);
-		this.#chart_data.push(data_item);
-		
+		// Didn't manage to find a good sopt, put it on the end
+		this.#chart_data.push({ label, data_item });
 	}
 	
 	clear() {
 		super.clear();
-		
-		this.chart_options.series[0].data.length = 0;
-		this.chart_options.xaxis.categories.length = 0;
-		
-		this.__update_chart();
+		this.el_gauge.replaceChildren();
+		this.chart = null;
+		this.#chart_data = [];
 	}
 	
 	append(peer, table) {
@@ -114,9 +113,8 @@ class UIGauge extends AbstractUIItem {
 		}
 		
 		this.#__insert_item(label, data_item);
-		console.log(`def ${this.def.name} | chart_labels`, this.#chart_labels);
 		console.log(`def ${this.def.name} | chart_data`, this.#chart_data);
-		this.#__update_chart();
+		this.render();
 	}
 }
 
diff --git a/src/static/js/ui/charts/ChartStackedBarSet.mjs b/src/static/js/ui/charts/ChartStackedBarSet.mjs
index d7a386b..2d6b96b 100644
--- a/src/static/js/ui/charts/ChartStackedBarSet.mjs
+++ b/src/static/js/ui/charts/ChartStackedBarSet.mjs
@@ -7,18 +7,29 @@ class ChartStackedBarSet {
 		this.emel = new Emel().emel;
 		
 		this.el = this.emel(`div[class="stacked-bar-set"]>div[class=legend]+div[class=bars]`);
+		this.el_legend = this.el.querySelector(`.legend`);
 		
 		this.bars = [];
 	}
 	
+	clear() {
+		this.el_legend.replaceChildren();
+		this.el.querySelector(".bars").replaceChildren();
+		this.bars = [];
+	}
+	
+	/**
+	 * Adds a new stacked bar to this set of stacked bars.
+	 * @param	{number}			i	The index in the list of stacked bars to insert it.
+	 * @param	{ChartStackedBar}	bar	The new stacked bar instance to add.
+	 * @return	{void}
+	 */
 	add_bar(i, bar) {
 		this.bars.splice(i, 0, bar);
-		this.#update_cats();
 	}
 	
 	#update_cats() {
-		const el_legend = this.el.querySelector(`.legend`);
-		const els_legend_items = [...el_legend.querySelectorAll(`.legend-item`)];
+		const els_legend_items = [...this.el_legend.querySelectorAll(`.legend-item`)];
 		
 		// map → flatten → uniq
 		let cats = this.bars.map(bar => bar.categories).flat()
