uPlot icon indicating copy to clipboard operation
uPlot copied to clipboard

Help needed with uPlot running on ESP32 microcontroller

Open happytm opened this issue 3 years ago • 1 comments

@leeoniya Hi Leon,

Thank you for such a powerful graph software which can run on microcontroller where webserver itself is hosted.

With your help I was able to implement graphs for my home automation project. In my opinion it is easiest and fastest way to visualize sensor data of whole network with very little effort. Everything is working fine for me.

Now I wanted to add some filters to load graph according to three dropdown menu at the top and I am not sure it is even possible.

I have shared relevant code from my project with mock up of three drop down menus. If it is possible to load graph according to selection at top can you please show me how. My code is below: Thanks.

My data.json structure is : [12,"Epoch","Location","Voltage","SSID","T1","S1","T2","S2","T3","S3","T4","S4"]

<html> 
<head> 
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> 
<title>Proberequest, TinyMQTT, Websockets, Asyncwebserver, LittleFS Editor & uPlot</title> 

<link rel="stylesheet" href="uPlot.min.css">

<style>
body {margin: 0;}
.u-legend.u-inline .u-value {width: 150px;text-align: left;}
		
#container { width: 100%; height: 49vh; background-color: #333; display: flex; align-items: center; justify-content: center; overflow: hidden; border-radius: 7px; touch-action: none; } 
#item { width: 100px; height: 100px; background-color: rgb(245, 230, 99); border: 10px solid rgba(136, 136, 136, .5); border-radius: 50%; touch-action: none; user-select: none; } 
#item:active { background-color: rgba(168, 218, 220, 1.00); } 
#item:hover { cursor: pointer; border-width: 20px; } 
#area { position: fixed; right: 33.33%; top: 50vh; } 
#stream-container{ height: 49vh; padding:0; margin:0; margin-block-start:0; margin-block-end:0; margin-inline-start:0; margin-inline-end:0 } 
#stream-container img{ display:block; max-width:100%; min-height:49vh; border-radius:4px; margin-top:8px } 
</style> 

</head> 

<body>
<script type="text/javascript" src="uPlot.iife.min.js"></script>

<h2>Proberequest, TinyMQTT, Asyncwebserver, LittleFS Editor & uPlot</h2>

<SELECT class="combine" id ="period" name = "period">
    <option value="all">Graph by Period</option>
    <option value="1">Last Hour</option>
    <option value="2">Today</option>
    <option value="3">This Week</option>
    <option value="4">This Month</option>
    <option value="5">This Year</option>
</SELECT>

<SELECT class="combine" id ="deviceid" name = "deviceid">
    <option value="all">Graph by Device</option>
    <option value="1">Gateway</option>
    <option value="6">Livingroom</option>
    <option value="16">Kitchen</option>
    <option value="26">Bedroom1</option>
    <option value="36">Bedroom2</option>
    <option value="46">Bathroom1</option>
    <option value="56">Bathroom2</option>
    <option value="66">Laundry</option>
    <option value="76">Solar Tracker</option>
    <option value="86">Water Tank</option>
    <option value="96">Weather Station</option>
    <option value="106">Greenhouse</option>
</SELECT>

<SELECT class="combine" id ="sensortype" name = "sensortype">
    <option value="all">Graph by Sensor Type</option>
    <option value="100">Voltage</option>
    <option value="200">RSSI</option>
    <option value="1">Temperature</option>
    <option value="2">Humidity</option>
    <option value="3">Pressure</option>
    <option value="4">Light</option>
    <option value="5">Level</option>
    <option value="6">Switch Status</option>
    <option value="7">Motion</option>
    <option value="8">Azimuth</option>
    <option value="9">Elevation</option>
    <option value="10">Soil Moisture</option>
    <option value="11">Wind Speed</option>
    <option value="12">Wind Direction</option>
    <option value="13">Rainfall</option>
    <option value="14">Air Quality</option>
</SELECT>
<script>

var sensor1,sensor2,sensor3,sensor4,sensor5,sensor6,sensor7,sensor8,sensor9,sensor10,sensor11,sensor12,sensor13,sensor14;

// converts the legend into a simple tooltip
function legendAsTooltipPlugin({ className, style = { backgroundColor:"rgba(255, 249, 196, 0.92)", color: "black" } } = {}) {
				let legendEl;

				function init(u, opts) {
					legendEl = u.root.querySelector(".u-legend");

					legendEl.classList.remove("u-inline");
					className && legendEl.classList.add(className);

					uPlot.assign(legendEl.style, {
						textAlign: "left",
						pointerEvents: "",
						display: "none",
						position: "absolute",
						left: 0,
						top: 0,
						zIndex: 100,
						boxShadow: "2px 2px 10px rgba(0,0,0,0.5)",
						...style
					});

					// hide series color markers
					const idents = legendEl.querySelectorAll(".u-marker");

					for (let i = 0; i < idents.length; i++)
						idents[i].style.display = "";

					const overEl = u.root.querySelector(".u-over");
					overEl.style.overflow = "visible";

					// move legend into plot bounds
					overEl.appendChild(legendEl);

					// show/hide tooltip on enter/exit
					overEl.addEventListener("mouseenter", () => {legendEl.style.display = null;});
					overEl.addEventListener("mouseleave", () => {legendEl.style.display = "none";});

					// let tooltip exit plot
			//	overEl.style.overflow = "visible";
				}

				function update(u) {
					const { left, top } = u.cursor;
					legendEl.style.transform = "translate(" + left + "px, " + top + "px)";
				}

				return {
					hooks: {
						init: init,
						setCursor: update,
					}
				};
			}
   
			function prepData(packed) {
				console.time("prep");

				// epoch,location,voltage,rssi,temperature,humidity,pressure,light

				const numFields = packed[0];
                console.log(numFields); //12
				packed = packed.slice(numFields + 1);
                console.log(packed);   //single array of all sensor data.
                
        
				let data = [
					Array(packed.length/numFields), // Time
					Array(packed.length/numFields), // Location
          Array(packed.length/numFields), // Voltage
					Array(packed.length/numFields), // RSSI
					Array(packed.length/numFields), // Temperature
					Array(packed.length/numFields), // Humidity
          Array(packed.length/numFields), // Pressure
					Array(packed.length/numFields), // Light
					Array(packed.length/numFields), // Level
					Array(packed.length/numFields), // Switch Status
					Array(packed.length/numFields), // Motion
					Array(packed.length/numFields), // Azimuth
					Array(packed.length/numFields), // Elevation
					Array(packed.length/numFields), // Soil Moisture
					Array(packed.length/numFields), // Wind Speed
					Array(packed.length/numFields), // Wind Direction
					Array(packed.length/numFields), // Rainfall
					Array(packed.length/numFields), // Air Quality
				];
				
			  console.log(data); // (12)arrays of values by field

				for (let i = 0, j = 0; i < packed.length; i += numFields, j++) {
				
					data[0][j] = packed[i+0];
				  data[1][j] = packed[i+1];
					data[2][j] = packed[i+2];
					data[3][j] = packed[i+3];
					
					       if (packed[i+4] == 1) {data[4][j] = packed[i+5]; sensor1   = "Temperature";
					} else if (packed[i+4] == 2) {data[5][j] = packed[i+5]; sensor2   = "Humidity";
					} else if (packed[i+4] == 3) {data[6][j] = packed[i+5] * 4; sensor3   = "Pressure";
					} else if (packed[i+4] == 4) {data[7][j] = packed[i+5]; sensor4   = "Light";
				  } else if (packed[i+4] == 5) {data[8][j] = packed[i+5]; sensor5   = "Level";
					} else if (packed[i+4] == 6) {data[9][j] = packed[i+5]; sensor6   = "Switch Status";
					} else if (packed[i+4] == 7) {data[10][j] = packed[i+5]; sensor7  = "Motion";
				  } else if (packed[i+4] == 8) {data[11][j] = packed[i+5]; sensor8  = "Azimuth";
				  } else if (packed[i+4] == 9) {data[12][j] = packed[i+5]; sensor9  = "Elevation";
					} else if (packed[i+4] == 10) {data[13][j] = packed[i+5];sensor10 = "Soil Moisture";
				  } else if (packed[i+4] == 11) {data[14][j] = packed[i+5];sensor11 = "Wind Speed";
					} else if (packed[i+4] == 12) {data[15][j] = packed[i+5];sensor12 = "Wind Direction";
					} else if (packed[i+4] == 13) {data[16][j] = packed[i+5];sensor13 = "Rainfall";
					} else if (packed[i+4] == 14) {data[17][j] = packed[i+5];sensor14 = "Air Quality";}
				  
					
					       if (packed[i+6] == 1) {data[4][j] = packed[i+7]; sensor1   = "Temperature";
					} else if (packed[i+6] == 2) {data[5][j] = packed[i+7]; sensor2   = "Humidity";
					} else if (packed[i+6] == 3) {data[6][j] = packed[i+7] * 4; sensor3   = "Pressure";
					} else if (packed[i+6] == 4) {data[7][j] = packed[i+7]; sensor4   = "Light";
				  } else if (packed[i+6] == 5) {data[8][j] = packed[i+7]; sensor5   = "Level";
					} else if (packed[i+6] == 6) {data[9][j] = packed[i+7]; sensor6   = "Switch Status";
					} else if (packed[i+6] == 7) {data[10][j] = packed[i+7]; sensor7  = "Motion";
				  } else if (packed[i+6] == 8) {data[11][j] = packed[i+7]; sensor8  = "Azimuth";
				  } else if (packed[i+6] == 9) {data[12][j] = packed[i+7]; sensor9  = "Elevation";
					} else if (packed[i+6] == 10) {data[13][j] = packed[i+7];sensor10 = "Soil Moisture";
				  } else if (packed[i+6] == 11) {data[14][j] = packed[i+7];sensor11 = "Wind Speed";
					} else if (packed[i+6] == 12) {data[15][j] = packed[i+7];sensor12 = "Wind Direction";
					} else if (packed[i+6] == 13) {data[16][j] = packed[i+7];sensor13 = "Rainfall";
					} else if (packed[i+6] == 14) {data[17][j] = packed[i+7];sensor14 = "Air Quality";}
					
					       if (packed[i+8] == 1) {data[4][j] = packed[i+9]; sensor1   = "Temperature";
					} else if (packed[i+8] == 2) {data[5][j] = packed[i+9]; sensor2   = "Humidity";
					} else if (packed[i+8] == 3) {data[6][j] = packed[i+9] * 4; sensor3   = "Pressure";
					} else if (packed[i+8] == 4) {data[7][j] = packed[i+9]; sensor4   = "Light";
				  } else if (packed[i+8] == 5) {data[8][j] = packed[i+9]; sensor5   = "Level";
					} else if (packed[i+8] == 6) {data[9][j] = packed[i+9]; sensor6   = "Switch Status";
					} else if (packed[i+8] == 7) {data[10][j] = packed[i+9]; sensor7  = "Motion";
				  } else if (packed[i+8] == 8) {data[11][j] = packed[i+9]; sensor8  = "Azimuth";
				  } else if (packed[i+8] == 9) {data[12][j] = packed[i+9]; sensor9  = "Elevation";
					} else if (packed[i+8] == 10) {data[13][j] = packed[i+9];sensor10 = "Soil Moisture";
				  } else if (packed[i+8] == 11) {data[14][j] = packed[i+9];sensor11 = "Wind Speed";
					} else if (packed[i+8] == 12) {data[15][j] = packed[i+9];sensor12 = "Wind Direction";
					} else if (packed[i+8] == 13) {data[16][j] = packed[i+9];sensor13 = "Rainfall";
					} else if (packed[i+8] == 14) {data[17][j] = packed[i+9];sensor14 = "Air Quality";}
					
					       if (packed[i+10] == 1) {data[4][j] = packed[i+11]; sensor1   = "Temperature";
					} else if (packed[i+10] == 2) {data[5][j] = packed[i+11]; sensor2   = "Humidity";
					} else if (packed[i+10] == 3) {data[6][j] = packed[i+11] * 4; sensor3   = "Pressure";
					} else if (packed[i+10] == 4) {data[7][j] = packed[i+11]; sensor4   = "Light";
				  } else if (packed[i+10] == 5) {data[8][j] = packed[i+11]; sensor5   = "Level";
					} else if (packed[i+10] == 6) {data[9][j] = packed[i+11]; sensor6   = "Switch Status";
					} else if (packed[i+10] == 7) {data[10][j] = packed[i+11]; sensor7  = "Motion";
				  } else if (packed[i+10] == 8) {data[11][j] = packed[i+11]; sensor8  = "Azimuth";
				  } else if (packed[i+10] == 9) {data[12][j] = packed[i+11]; sensor9  = "Elevation";
					} else if (packed[i+10] == 10) {data[13][j] = packed[i+11];sensor10 = "Soil Moisture";
				  } else if (packed[i+10] == 11) {data[14][j] = packed[i+11];sensor11 = "Wind Speed";
					} else if (packed[i+10] == 12) {data[15][j] = packed[i+11];sensor12 = "Wind Direction";
					} else if (packed[i+10] == 13) {data[16][j] = packed[i+11];sensor13 = "Rainfall";
					} else if (packed[i+10] == 14) {data[17][j] = packed[i+11];sensor14 = "Air Quality";}					
					
			}

		    
				console.timeEnd("prep");

				return data;
				
			}


			function makeChart(data) {
				console.time("chart");
				console.log(data[4][1]); // SensorType1
   
  
    
		var title = "Environment data from sensor network";
		const opts = {
					title: title,
					
					width: 480,
					height:600,
				//	ms:     1,
				//	cursor: {
				//		x: false,
				//		y: false,
				//	},
					series: [
						{},
					
						{
							label: "Device",
							scale: "Left",
							value: (u, v) => v == null ? "-" : v.toFixed(1) + " ",
							stroke: "teal",
							width: 1/devicePixelRatio,
						},
						{
							label: "Voltage",
							scale: "Left",
							value: (u, v) => v == null ? "-" : v.toFixed(1) + " V",
							stroke: "green",
							width: 1/devicePixelRatio,
						},
						{
							label: "RSSI",
							scale: "Right",
							value: (u, v) => v == null ? "-" : v.toFixed(2) + " %",
							stroke: "maroon",
							width: 1/devicePixelRatio,
						},
						
						{
							label: sensor1,
							scale: "Left",
							value: (u, v) => v == null ? "-" : v.toFixed(1) + " F",
							stroke: "lime",
							width: 1/devicePixelRatio,
						},
						
						{
							label: sensor2,
							scale: "Left",
							value: (u, v) => v == null ? "-" : v.toFixed(1) + " %",
							stroke: "olive",
							width: 1/devicePixelRatio,
						},
						
						{
							label: sensor3,
							scale: "Right",
							value: (u, v) => v == null ? "-" : v.toFixed(2) + " mb",
							stroke: "blue",
							width: 1/devicePixelRatio,
						},
						
						{
							label: sensor4,
							scale: "Left",
							value: (u, v) => v == null ? "-" : v.toFixed(1) + " %",
							stroke: "orange",
							width: 1/devicePixelRatio,
						},
						
            {
							label: "Level",
							scale: "Left",
							value: (u, v) => v == null ? "-" : v.toFixed(1) + " ",
							stroke: "crimson",
							width: 1/devicePixelRatio,
						},
						{
							label: "Switch Status",
							scale: "Left",
							value: (u, v) => v == null ? "-" : v.toFixed(1) + " V",
							stroke: "deeppink",
							width: 1/devicePixelRatio,
						},
						{
							label: "Motion",
							scale: "Right",
							value: (u, v) => v == null ? "-" : v.toFixed(2) + " %",
							stroke: "tomato",
							width: 1/devicePixelRatio,
						},
						{
							label: "Azimuth",
							scale: "Left",
							value: (u, v) => v == null ? "-" : v.toFixed(1) + " V",
							stroke: "yellow",
							width: 1/devicePixelRatio,
						},
						{
							label: "Elevation",
							scale: "Right",
							value: (u, v) => v == null ? "-" : v.toFixed(2) + " %",
							stroke: "magenta",
							width: 1/devicePixelRatio,
						},
						{
							label: "Soil Moisture",
							scale: "Left",
							value: (u, v) => v == null ? "-" : v.toFixed(1) + " V",
							stroke: "indigo",
							width: 1/devicePixelRatio,
						},
						{
							label: "Wind Speed",
							scale: "Right",
							value: (u, v) => v == null ? "-" : v.toFixed(2) + " %",
							stroke: "springgreen",
							width: 1/devicePixelRatio,
						},
						{
							label: "Wind Direction",
							scale: "Left",
							value: (u, v) => v == null ? "-" : v.toFixed(1) + " V",
							stroke: "cyan",
							width: 1/devicePixelRatio,
						},
						{
							label: "Rainfall",
							scale: "Right",
							value: (u, v) => v == null ? "-" : v.toFixed(2) + " %",
							stroke: "navy",
							width: 1/devicePixelRatio,
						},
						{
							label: "Air Quality",
							scale: "Left",
							value: (u, v) => v == null ? "-" : v.toFixed(1) + " V",
							stroke: "brown",
							width: 1/devicePixelRatio,
						}
						
						
	  		],
					
					plugins: [
					
					legendAsTooltipPlugin()
					
				],
					
					axes: [
						{},
						
						{
							scale: "Left",
							values: (u, vals, space) => vals.map(v => +v.toFixed(1) + " "),
						},
						{
							side: 1,
							scale: "Right",
							size: 60,
							values: (u, vals, space) => vals.map(v => +v.toFixed(2) + ""),
							grid: {show: false},
						},
					],
				};
              
				let uplot = new uPlot(opts, data, document.body);
        
				Promise.resolve().then(() => {
					
					console.timeEnd("chart");
				});
			}

			fetch("data.json").then(r => r.json()).then(packed => {
				
				let data = prepData(packed);
				setTimeout(() => makeChart(data), 0);
			});
</script>

</body>
</html>   

happytm avatar May 23 '22 05:05 happytm

Sorry I forgot to include data.json sample above.

[12,"Epoch","Location","Voltage","SSID","T1","S1","T2","S2","T3","S3","T4","S4", 1653449039,6,2.30,-48,1,73,2,54,3,252,4,68, 1653449040,16,2.30,-48,2,52,3,242,4,67,5,78, 1653449100,26,2.30,-48,3,241,4,50,5,1,6,0, 1653449160,36,2.30,-46,4,70,5,0,6,0,7,1, 1653449220,46,2.30,-47,5,1,6,1,7,0,8,77, 1653449280,56,2.30,-47,6,73,7,1,8,78,9,44, 1653449280,66,2.30,-48,7,0,8,79,9,45,10,60, 1653449340,76,2.30,-46,8,80,9,46,10,61,11,5, 1653449400,86,2.30,-47,9,47,10,62,11,7,12,23, 1653449400,96,2.30,-47,10,63,11,4,12,24,13,2, 1653449460,106,2.30,-45,11,6,12,23,13,2,14,49]

Thanks.

happytm avatar May 25 '22 04:05 happytm