Merge branch 'main' of gitlab.fdmci.hva.nl:propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59
This commit is contained in:
7
arduino/Screen code/Screen-code-final/.theia/launch.json
Normal file
7
arduino/Screen code/Screen-code-final/.theia/launch.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
]
|
||||
}
|
115
arduino/Screen code/Screen-code-final/Screen-code-final.ino
Normal file
115
arduino/Screen code/Screen-code-final/Screen-code-final.ino
Normal file
@@ -0,0 +1,115 @@
|
||||
#include "headerFile.h"
|
||||
|
||||
int i = 0;
|
||||
int questionID = 1;
|
||||
char* Question[] = {
|
||||
"How clean are the toilets?",
|
||||
"How clean is the study area?",
|
||||
"What do you think of the temperature in the study area?",
|
||||
"How crowded would you say the study area is?",
|
||||
"Is there enough help available?"
|
||||
};
|
||||
|
||||
char* Answer[] = {
|
||||
"clean, normal, disgusting",
|
||||
"clean, normal, disgusting",
|
||||
"hot, perfect, cold",
|
||||
"not at all, its fine, really crowded",
|
||||
"no, decently, yes"
|
||||
};
|
||||
|
||||
Adafruit_ST7796S_kbv tft = Adafruit_ST7796S_kbv(TFT_CS, TFT_DC, MOSI, SCK, TFT_RST, MISO);
|
||||
DisplayText displayText(tft);
|
||||
|
||||
void setup() {
|
||||
//buttonpins
|
||||
pinMode(16, INPUT_PULLDOWN);
|
||||
pinMode(17, INPUT_PULLDOWN);
|
||||
pinMode(18, INPUT_PULLDOWN);
|
||||
Serial.begin(9600);
|
||||
|
||||
tft.begin(); // Initialize the display
|
||||
tft.setRotation(3); // Set the rotation to horizontal
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
displayText.writeText("Loading...", 3, 0, 200, 0, true, true);
|
||||
websocketSetup();
|
||||
}
|
||||
|
||||
|
||||
void loop() {
|
||||
webSocket.loop();
|
||||
screenButtonHandler();
|
||||
}
|
||||
|
||||
void websocketSetup(){
|
||||
WiFiMulti.addAP("iotroam", "vbK9gbDBIB");
|
||||
WiFiMulti.addAP("ObsidianAmstelveen", "drijversstraatmaastricht");
|
||||
|
||||
while(WiFiMulti.run() != WL_CONNECTED) {
|
||||
delay(100);
|
||||
}
|
||||
|
||||
// server address, port and URL
|
||||
webSocket.begin("145.92.8.114", 80, "/ws");
|
||||
// try ever 500 again if connection has failed
|
||||
webSocket.setReconnectInterval(500);
|
||||
}
|
||||
|
||||
void screenButtonHandler(){
|
||||
|
||||
int redButton = digitalRead(16);
|
||||
int whiteButton = digitalRead(17);
|
||||
int greenButton = digitalRead(18);
|
||||
|
||||
if (initialized) {
|
||||
initialized = false;
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
displayText.writeText(Question[i], 3, 0, 0, 0, true, false);
|
||||
displayText.writeText(Answer[i], 3, 0, 200, 0, true, true);
|
||||
}
|
||||
|
||||
if (redButton == HIGH){
|
||||
sendData(questionID, "0");
|
||||
}
|
||||
if (whiteButton == HIGH){
|
||||
sendData(questionID, "1");
|
||||
}
|
||||
if (greenButton == HIGH){
|
||||
sendData(questionID, "2");
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (redButton || whiteButton || greenButton) {
|
||||
i++;
|
||||
questionID++;
|
||||
if (questionID == 6){
|
||||
questionID = 1;
|
||||
}
|
||||
if (i == 5) {
|
||||
i = 0;
|
||||
}
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
displayText.writeText(Question[i], 3, 0, 0, 0, true, false);
|
||||
displayText.writeText(Answer[i], 3, 0, 200, 0, true, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void sendData(int question, String answer){
|
||||
webSocket.sendTXT("{\"node\": \"" + String(WiFi.macAddress()) + "\", \"Response\":\"" + String(answer) + "\",\"QuestionID\":\"" + String(question) + "\"}");
|
||||
}
|
||||
|
||||
void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) {
|
||||
const uint8_t* src = (const uint8_t*) mem;
|
||||
USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);
|
||||
for(uint32_t i = 0; i < len; i++) {
|
||||
if(i % cols == 0) {
|
||||
USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);
|
||||
}
|
||||
USE_SERIAL.printf("%02X ", *src);
|
||||
src++;
|
||||
}
|
||||
USE_SERIAL.printf("\n");
|
||||
}
|
||||
|
22
arduino/Screen code/Screen-code-final/headerFile.h
Normal file
22
arduino/Screen code/Screen-code-final/headerFile.h
Normal file
@@ -0,0 +1,22 @@
|
||||
//screen stuff
|
||||
#include <SPI.h>
|
||||
#include "Adafruit_GFX.h"
|
||||
#include "Adafruit_ST7796S_kbv.h"
|
||||
#include "displayText.h"
|
||||
|
||||
#define TFT_CS 14
|
||||
#define TFT_DC 13
|
||||
#define TFT_RST 12
|
||||
#define MOSI 11
|
||||
#define SCK 10
|
||||
#define MISO 9
|
||||
|
||||
//websocket stuff
|
||||
#include <WiFiMulti.h>
|
||||
#include <WiFi.h>
|
||||
#include <WebSocketsClient.h>
|
||||
#define USE_SERIAL Serial
|
||||
|
||||
WiFiMulti WiFiMulti;
|
||||
WebSocketsClient webSocket;
|
||||
bool initialized = true;
|
106
arduino/Screen code/screen-code-class/displayText.cpp
Normal file
106
arduino/Screen code/screen-code-class/displayText.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
#include "displayText.h"
|
||||
#include "Arduino.h"
|
||||
|
||||
//constructor
|
||||
DisplayText::DisplayText(Adafruit_ST7796S_kbv& tftDisplay) : tft(tftDisplay) {
|
||||
tft.setCursor(0,0);
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
}
|
||||
//display text public function
|
||||
void DisplayText::writeText(char* text, int size, int posX, int posY, int screenTime, bool center, bool bottom) {
|
||||
if (center) {
|
||||
posX = centerText(text);
|
||||
}
|
||||
// if (bottom) {
|
||||
// posY = bottomText(text);
|
||||
// }
|
||||
tft.setCursor(posX, posY);
|
||||
tft.setTextSize(size);
|
||||
printWordsFull(text, bottom);
|
||||
delay(screenTime);
|
||||
}
|
||||
|
||||
//to center the text when enabled in the public function
|
||||
int DisplayText::centerText(char* text) {
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
tft.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
|
||||
int x = (tft.width() - w) / 2;
|
||||
return x;
|
||||
}
|
||||
|
||||
// //to display the text at the bottom when enabled in the public function
|
||||
// int DisplayText::bottomText(char* text) {
|
||||
// int16_t x1, y1;
|
||||
// uint16_t w, h;
|
||||
// tft.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
|
||||
// int y = (tft.height() - h);
|
||||
// return y;
|
||||
// }
|
||||
|
||||
//attempt to write the text out in full (wip)
|
||||
void DisplayText::printWordsFull(char* text, bool bottom) {
|
||||
const int screenWidth = 480; // replace with your TFT display width
|
||||
const int lineHeight = 30; // replace with your text line height
|
||||
//the double copy is needed so it doesnt alter the original text
|
||||
char* newtext1 = strdup(text); // Create a copy of the string for the first strtok_r
|
||||
char* newtext2 = strdup(text); // Create a second copy for the second strtok_r
|
||||
char* saveptr1, *saveptr2;
|
||||
char* word = strtok_r(newtext1, " ", &saveptr1);
|
||||
char line[100] = "";
|
||||
int lineCount = 0;
|
||||
int lineWidth = 0;
|
||||
|
||||
// Calculate total number of lines
|
||||
int totalLines = 0;
|
||||
char* tempWord = strtok_r(newtext2, " ", &saveptr2);
|
||||
while (tempWord != NULL) {
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
tft.getTextBounds(tempWord, 0, 0, &x1, &y1, &w, &h);
|
||||
if (lineWidth + w > screenWidth && strlen(line) > 0) {
|
||||
//if the line width is greater than the screen width, then we need to add a new line
|
||||
totalLines++;
|
||||
lineWidth = w;
|
||||
//otherwise we just add the width of the word to the line width
|
||||
} else {
|
||||
lineWidth += w;
|
||||
}
|
||||
tempWord = strtok_r(NULL, " ", &saveptr2);
|
||||
}
|
||||
totalLines++; // Add one for the last line
|
||||
|
||||
// Reset variables for actual printing
|
||||
strcpy(line, "");
|
||||
lineWidth = 0;
|
||||
|
||||
while (word != NULL) {
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
tft.getTextBounds(word, 0, 0, &x1, &y1, &w, &h);
|
||||
|
||||
if (lineWidth + w > screenWidth && strlen(line) > 0) {
|
||||
int y = bottom ? tft.height() - lineHeight * (totalLines - lineCount) : lineHeight * lineCount;
|
||||
tft.setCursor(0, y);
|
||||
tft.println(line);
|
||||
lineCount++;
|
||||
strcpy(line, word);
|
||||
strcat(line, " ");
|
||||
lineWidth = w;
|
||||
} else {
|
||||
strcat(line, word);
|
||||
strcat(line, " ");
|
||||
lineWidth += w;
|
||||
}
|
||||
|
||||
word = strtok_r(NULL, " ", &saveptr1);
|
||||
}
|
||||
|
||||
// print the last line
|
||||
int y = bottom ? tft.height() - lineHeight * (totalLines - lineCount) : lineHeight * lineCount;
|
||||
tft.setCursor(0, y);
|
||||
tft.println(line);
|
||||
|
||||
free(newtext1); // Free the memory allocated by strdup
|
||||
free(newtext2); // Free the memory allocated by strdup
|
||||
}
|
19
arduino/Screen code/screen-code-class/displayText.h
Normal file
19
arduino/Screen code/screen-code-class/displayText.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef DISPLAYTEXT_H
|
||||
#define DISPLAYTEXT_H
|
||||
|
||||
#include "Adafruit_GFX.h"
|
||||
#include "Adafruit_ST7796S_kbv.h"
|
||||
|
||||
class DisplayText {
|
||||
private:
|
||||
Adafruit_ST7796S_kbv& tft;
|
||||
int centerText(char* text);
|
||||
// int bottomText(char* text);
|
||||
void printWordsFull(char* text, bool bottom);
|
||||
|
||||
public:
|
||||
DisplayText(Adafruit_ST7796S_kbv& tftDisplay);
|
||||
void writeText(char* text, int size, int posX, int posY, int screenTime, bool center, bool bottom);
|
||||
};
|
||||
|
||||
#endif
|
@@ -9,7 +9,7 @@ Questions shouldn't be able to measure using sensors. 3 possible answers.
|
||||
- How clean is the study area? (clean, normal, disgusting).
|
||||
- What do you think of the temperature in the study area? (hot, perfect, cold).
|
||||
- How crowded would you say the study area is?(not at all, its fine, really crowded).
|
||||
- Is there enough help available? (no, decently, yes).
|
||||
- Is there enough help available on the 5th floor? (no, decently, yes).
|
||||
|
||||
### Feedback questions
|
||||
|
||||
|
@@ -61,4 +61,29 @@ graph LR
|
||||
A[Design Sketch] -->|Usertest| B(Fusion360 design)
|
||||
B -->|Not fitting| C[New fusion360 design]
|
||||
C -->|assembly| D[Finished product]
|
||||
```
|
||||
```
|
||||
|
||||
## The evolved design of the nodes.
|
||||
During the last sprint we had a lot of feeback about the node and mostly about tis design. hence why we had decided to remake it in several different ways.
|
||||
|
||||
We had made a prototype for a wooden version, but that never made it past an first sketch, seeing as almost all fedback came back telling About how the plastic design looked better and was less distreacting.
|
||||
The user-test document is here:( https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/blob/main/docs/brainstorm/Feedback.md?ref_type=heads#design-questions-led-by-bram )
|
||||
|
||||
After the results came back, we decided to make progress with further designing the cover of the node.
|
||||
The main body shape was certain, because all of the components needed to fit inside of the node and needed some space.
|
||||
Aswell as the simple shape to save on printing costs and keep the design simple to look at.
|
||||
|
||||
The only thing that was able to change was the cover.
|
||||
The cover was originally made of solid plastic and was molded to fit the shape of the board and the sensors/ screen inside.
|
||||
|
||||
From here we decided to brainstorm on an easyer way of covering the circurty.
|
||||
|
||||
That is when it hit us, We could use acrylic as a cover!
|
||||
|
||||
Acrylic can come in many different colors, is easyer to cut out ( takes less time) and gives a bit of an isight into the wiring.
|
||||
|
||||
This this in mind we made a node with this new design and remade one with the old design, this again could be further used for user-tests.
|
||||
|
||||
For the time being we are able to show off both of the designs and are able to read data from both, the only difference being the outer shell.
|
||||
|
||||
The images:
|
@@ -3,12 +3,14 @@ import mysql.connector
|
||||
from queries import *
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
@app.route('/getMeasurements')
|
||||
def index():
|
||||
node = request.args.get('node', default = None)
|
||||
dataType = request.args.get('dataType', default = None)
|
||||
MAC = request.args.get('MAC', default = None)
|
||||
return getData(node, dataType, MAC)
|
||||
dateStart = request.args.get('dateStart', default = None)
|
||||
dateEnd = request.args.get('dateEnd', default = None)
|
||||
return getData(node, dataType, MAC, dateStart, dateEnd)
|
||||
|
||||
@app.route('/updateData')
|
||||
def updateDataIndex():
|
||||
@@ -22,9 +24,13 @@ def getNodeInfoIndex():
|
||||
macAdress = request.args.get('macAdress', None)
|
||||
return getNodeInfo(macAdress)
|
||||
|
||||
@app.route('/getQuestionData')
|
||||
def getQuestionDataIndex():
|
||||
return getQuestionData()
|
||||
|
||||
def updateData(node, name, location):
|
||||
mydb = loginDB()
|
||||
query = update_query(node, name, location)
|
||||
query = update_query(node, name, location, False, False)
|
||||
cursor = mydb.cursor(dictionary=True) # Enable dictionary output
|
||||
cursor.execute(query)
|
||||
mydb.commit()
|
||||
@@ -43,9 +49,9 @@ def loginDB():
|
||||
)
|
||||
return mydb
|
||||
|
||||
def getData(node, dataType, MAC):
|
||||
def getData(node, dataTypes, MAC, dateStart, dateEnd):
|
||||
mydb = loginDB()
|
||||
query = get_query(node, dataType, MAC)
|
||||
query = get_query(node, dataTypes, MAC, False, False, dateStart, dateEnd)
|
||||
cursor = mydb.cursor(dictionary=True) # Enable dictionary output
|
||||
cursor.execute(query)
|
||||
result = cursor.fetchall() # Fetch the results
|
||||
@@ -56,7 +62,7 @@ def getData(node, dataType, MAC):
|
||||
|
||||
def getNodeInfo(macAdress):
|
||||
mydb = loginDB()
|
||||
query = get_query(False, False, macAdress)
|
||||
query = get_query(False, False, macAdress, False, False, False, False)
|
||||
cursor = mydb.cursor(dictionary=True) # Enable dictionary output
|
||||
cursor.execute(query)
|
||||
result = cursor.fetchall() # Fetch the results
|
||||
@@ -65,5 +71,16 @@ def getNodeInfo(macAdress):
|
||||
|
||||
return result
|
||||
|
||||
def getQuestionData(questionID, replies):
|
||||
mydb = loginDB()
|
||||
query = get_query(False, False, False, questionID, replies)
|
||||
cursor = mydb.cursor(dictionary=True) # Enable dictionary output
|
||||
cursor.execute(query)
|
||||
result = cursor.fetchall() # Fetch the results
|
||||
cursor.close()
|
||||
mydb.close()
|
||||
|
||||
return result
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='localhost')
|
||||
|
@@ -1,18 +1,38 @@
|
||||
def get_query(node, dataType, MAC):
|
||||
if node and dataType:
|
||||
def get_query(node, dataType, MAC, questionID, replies, dateStart, dateEnd):
|
||||
if dateStart and dateEnd and node and dataType:
|
||||
query = f'''SELECT *
|
||||
FROM Measurement
|
||||
WHERE TimeStamp BETWEEN '{dateStart}' AND '{dateEnd}' AND NodeID = {node} AND Type IN ('{dataType}');'''
|
||||
elif dateStart and dateEnd and node:
|
||||
query = f'''SELECT *
|
||||
FROM Measurement
|
||||
WHERE TimeStamp BETWEEN '{dateStart}' AND '{dateEnd}' AND NodeID = {node};'''
|
||||
elif dateStart and dateEnd:
|
||||
query = f'''SELECT *
|
||||
FROM Measurement
|
||||
WHERE TimeStamp BETWEEN '{dateStart}' AND '{dateEnd}';'''
|
||||
elif node and dataType:
|
||||
query = f"SELECT * FROM Measurement WHERE NodeID = {node} AND Type = '{dataType}'"
|
||||
elif MAC == "*":
|
||||
query = "SELECT * FROM Node"
|
||||
elif node:
|
||||
query = f"SELECT * FROM Measurement WHERE NodeID = {node}"
|
||||
elif dataType:
|
||||
query = f"SELECT * FROM Measurement WHERE Type = '{dataType}'"
|
||||
elif MAC:
|
||||
query = f"SELECT * FROM Node WHERE MAC = '{MAC}'"
|
||||
elif replies and questionID:
|
||||
query = f"SELECT * FROM Reply WHERE replies = '{replies}' AND QuestionID = '{questionID}'"
|
||||
elif questionID:
|
||||
query = f"SELECT * FROM Question"
|
||||
elif replies:
|
||||
query = f"SELECT * FROM Reply"
|
||||
|
||||
else:
|
||||
query = "SELECT * FROM `Measurement`"
|
||||
return query
|
||||
|
||||
|
||||
|
||||
def update_query(node, name, location):
|
||||
if node and name and location:
|
||||
query = f"""
|
||||
|
@@ -1,64 +1,19 @@
|
||||
import asyncio
|
||||
import websockets
|
||||
import mysql.connector
|
||||
import json
|
||||
from class_SensorNode import SensorNode
|
||||
from class_enqueteNode import EnqueteNode
|
||||
from classes_data import dbLogin
|
||||
|
||||
sensorNodeArray = []
|
||||
enqueteNodeArray = []
|
||||
|
||||
async def processSensorNodeData(data, nodeID):
|
||||
try:
|
||||
mydb = dbLogin()
|
||||
cursor = mydb.cursor()
|
||||
|
||||
query = "INSERT INTO `Measurement` (NodeID, Type, Value) VALUES (%s, %s, %s)"
|
||||
processedData = json.loads(data)
|
||||
|
||||
processedTemp = (processedData['Temp'])
|
||||
processedHumi = (processedData['Humi'])
|
||||
processedeCO2 = (processedData['eCO2'])
|
||||
processedTVOC = (processedData['TVOC'])
|
||||
processedMAC = (processedData['node'])
|
||||
|
||||
pushingDataArray = [(nodeID, "Temp", processedTemp), (nodeID, "Humi", processedHumi), (nodeID, "eCO2", processedeCO2), (nodeID, "TVOC", processedTVOC)]
|
||||
for i in pushingDataArray:
|
||||
print(query ,i)
|
||||
cursor.execute(query, i)
|
||||
mydb.commit()
|
||||
|
||||
except mysql.connector.Error as err:
|
||||
print("MySQL Error:", err)
|
||||
finally:
|
||||
cursor.close()
|
||||
mydb.close()
|
||||
|
||||
async def processEnqueteNodeData(data, nodeID):
|
||||
try:
|
||||
mydb = dbLogin()
|
||||
cursor = mydb.cursor()
|
||||
|
||||
query = "INSERT INTO `Reply` (Result, Node_NodeID, Question_QuestionID) VALUES (%s, %s, %s)"
|
||||
processedData = json.loads(data)
|
||||
|
||||
processedQuestionID = (processedData['QuestionID'])
|
||||
processedResponse = (processedData['Response'])
|
||||
|
||||
pushingDataArray = [(processedResponse, nodeID, processedQuestionID)]
|
||||
|
||||
for i in pushingDataArray:
|
||||
cursor.execute(query, i)
|
||||
mydb.commit()
|
||||
except mysql.connector.Error as err:
|
||||
print("MySQL Error:", err)
|
||||
finally:
|
||||
cursor.close()
|
||||
mydb.close()
|
||||
|
||||
async def receive_data():
|
||||
uri = "ws://145.92.8.114/ws"
|
||||
try:
|
||||
async with websockets.connect(uri) as websocket:
|
||||
while True:
|
||||
print("true")
|
||||
data = await websocket.recv()
|
||||
print(f"Received data: {data}")
|
||||
|
||||
@@ -73,14 +28,13 @@ async def receive_data():
|
||||
await getNodeInfo('sensor')
|
||||
await getNodeInfo('enquete')
|
||||
|
||||
print(sensorNodeArray)
|
||||
|
||||
if macAdress in sensorNodeArray:
|
||||
nodeID = await getNodeID(macAdress)
|
||||
await processSensorNodeData(data, nodeID)
|
||||
await SensorNode.processSensorNodeData(data, nodeID)
|
||||
elif macAdress in enqueteNodeArray:
|
||||
nodeID = await getNodeID(macAdress)
|
||||
await processEnqueteNodeData(data, nodeID)
|
||||
await EnqueteNode.processEnqueteNodeData(data, nodeID)
|
||||
else:
|
||||
await newNode(macAdress, type)
|
||||
except websockets.ConnectionClosedError as e:
|
||||
@@ -89,17 +43,8 @@ async def receive_data():
|
||||
async def main():
|
||||
await receive_data()
|
||||
|
||||
def dbLogin():
|
||||
mydb = mysql.connector.connect(
|
||||
host="localhost",
|
||||
user="root",
|
||||
password="Dingleberries69!",
|
||||
database="NodeData"
|
||||
)
|
||||
|
||||
return mydb
|
||||
|
||||
async def getNodeInfo(type):
|
||||
print("getNodeINfo")
|
||||
global sensorNodeArray
|
||||
global enqueteNodeArray
|
||||
|
||||
@@ -114,14 +59,12 @@ async def getNodeInfo(type):
|
||||
for tuples in nodeInfo:
|
||||
for item in tuples:
|
||||
nodeInfoArray.append(item)
|
||||
print(nodeInfoArray)
|
||||
|
||||
cursor.close()
|
||||
mydb.close()
|
||||
|
||||
if type == 'sensor':
|
||||
sensorNodeArray = nodeInfoArray
|
||||
print(sensorNodeArray)
|
||||
return sensorNodeArray
|
||||
|
||||
elif type == 'enquete':
|
||||
@@ -143,7 +86,7 @@ async def getNodeID(macAdress):
|
||||
return nodeID
|
||||
|
||||
async def newNode(mac, type):
|
||||
id = (mac, type,)
|
||||
id = (mac, type)
|
||||
mydb = dbLogin()
|
||||
|
||||
cursor = mydb.cursor()
|
26
server/web-data-connection/databaseGeneralClass.py
Normal file
26
server/web-data-connection/databaseGeneralClass.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import mysql.connector
|
||||
|
||||
def dbLogin():
|
||||
mydb = mysql.connector.connect(
|
||||
host="localhost",
|
||||
user="root",
|
||||
password="Dingleberries69!",
|
||||
database="NodeData"
|
||||
)
|
||||
return mydb
|
||||
|
||||
class Node():
|
||||
def __init__(self, macAdress):
|
||||
self.macAdress = macAdress
|
||||
self.id = None
|
||||
|
||||
def getNodeId(self):
|
||||
id = (self.macAdress,)
|
||||
mydb = dbLogin()
|
||||
cursor = mydb.cursor()
|
||||
cursor.execute("""SELECT nodeID FROM Node WHERE MAC = %s""", id)
|
||||
data = cursor.fetchall()
|
||||
|
||||
for tuples in data:
|
||||
for item in tuples:
|
||||
self.id = item
|
35
server/web-data-connection/enqueteNodeClass.py
Normal file
35
server/web-data-connection/enqueteNodeClass.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import mysql.connector
|
||||
import json
|
||||
|
||||
from classes_data import Node
|
||||
from classes_data import dbLogin
|
||||
|
||||
class EnqueteNode(Node):
|
||||
query = "INSERT INTO `Reply` (Result, Node_NodeID, Question_QuestionID) VALUES (%s, %s, %s)"
|
||||
|
||||
def __init__(self, macAdress, response, questionID):
|
||||
super().__init__(macAdress)
|
||||
self.response = response
|
||||
self.questionID = questionID
|
||||
|
||||
async def processEnqueteNodeData(data, nodeID):
|
||||
try:
|
||||
mydb = dbLogin()
|
||||
cursor = mydb.cursor()
|
||||
|
||||
processedData = json.loads(data)
|
||||
|
||||
EnqueteNode.questionID = (processedData['QuestionID'])
|
||||
EnqueteNode.response = (processedData['Response'])
|
||||
|
||||
pushingDataArray = [(EnqueteNode.questionID, nodeID, EnqueteNode.response)]
|
||||
|
||||
for i in pushingDataArray:
|
||||
print(EnqueteNode.query, i)
|
||||
cursor.execute(EnqueteNode.query, i)
|
||||
mydb.commit()
|
||||
except mysql.connector.Error as err:
|
||||
print("MySQL Error:", err)
|
||||
finally:
|
||||
cursor.close()
|
||||
mydb.close()
|
41
server/web-data-connection/sensorNodeClass.py
Normal file
41
server/web-data-connection/sensorNodeClass.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import mysql.connector
|
||||
import json
|
||||
|
||||
from classes_data import Node
|
||||
from classes_data import dbLogin
|
||||
|
||||
class SensorNode(Node):
|
||||
query = "INSERT INTO `Measurement` (NodeID, Type, Value) VALUES (%s, %s, %s)"
|
||||
|
||||
def __init__(self, macAdress, temp, humi, eCO2, TVOC):
|
||||
super().__init__(macAdress)
|
||||
self.temperature = temp
|
||||
self.humidity = humi
|
||||
self.eCO2 = eCO2
|
||||
self.TVOC = TVOC
|
||||
|
||||
async def processSensorNodeData(data, nodeID):
|
||||
try:
|
||||
mydb = dbLogin()
|
||||
cursor = mydb.cursor()
|
||||
|
||||
processedData = json.loads(data)
|
||||
|
||||
SensorNode.temperature = (processedData['Temp'])
|
||||
SensorNode.humidity = (processedData['Humi'])
|
||||
SensorNode.eCO2 = (processedData['eCO2'])
|
||||
SensorNode.TVOC = (processedData['TVOC'])
|
||||
|
||||
|
||||
|
||||
pushingDataArray = [(nodeID, "Temp", SensorNode.temperature), (nodeID, "Humi", SensorNode.humidity), (nodeID, "eCO2", SensorNode.eCO2), (nodeID, "TVOC", SensorNode.TVOC)]
|
||||
for i in pushingDataArray:
|
||||
cursor.execute(SensorNode.query, i)
|
||||
mydb.commit()
|
||||
|
||||
|
||||
except mysql.connector.Error as err:
|
||||
print("MySQL Error:", err)
|
||||
finally:
|
||||
cursor.close()
|
||||
mydb.close()
|
@@ -13,6 +13,7 @@ class GaugeGroup {
|
||||
this.element.innerHTML = `
|
||||
<h2 class="groupTitle">${this.nodeId} - ${this.location}</h2>
|
||||
<div class="Node">
|
||||
<img src="arrow.png" class="arrowimg" onclick="toggleGraph('${this.nodeId}')">
|
||||
${Array(this.gaugesCount).fill().map((_, i) => `
|
||||
<div class="Sensorvalues">
|
||||
<div id="gaugeContainer${this.nodeId}_${i+1}" class="gaugeContainer">
|
||||
@@ -25,6 +26,9 @@ class GaugeGroup {
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
<div class="plotly-container">
|
||||
<div id="liveGraph${this.nodeId}" class="disabled" ></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Append the new div to the body
|
||||
|
157
web/newWebsite/graph-classes.js
Normal file
157
web/newWebsite/graph-classes.js
Normal file
@@ -0,0 +1,157 @@
|
||||
class Graph {
|
||||
constructor(id) {
|
||||
this.id = "graph" + id;
|
||||
this.timeArray = [];
|
||||
this.tempArray = [];
|
||||
this.humiArray = [];
|
||||
this.eco2Array = [];
|
||||
this.tvocArray = [];
|
||||
}
|
||||
|
||||
// Function to create a graph
|
||||
makeGraph(line, lineColor, name) {
|
||||
let div = document.createElement("div");
|
||||
div.setAttribute("id", this.id);
|
||||
document.body.appendChild(div);
|
||||
let lineArray;
|
||||
switch (line) {
|
||||
case "temp":
|
||||
lineArray = this.tempArray;
|
||||
break;
|
||||
case "humi":
|
||||
lineArray = this.humiArray;
|
||||
break;
|
||||
case "eco2":
|
||||
lineArray = this.eco2Array;
|
||||
break;
|
||||
case "tvoc":
|
||||
lineArray = this.tvocArray;
|
||||
break;
|
||||
default:
|
||||
console.error("Invalid line");
|
||||
}
|
||||
Plotly.plot(this.id, [
|
||||
{
|
||||
x: this.timeArray,
|
||||
y: lineArray,
|
||||
mode: "lines",
|
||||
line: { color: lineColor },
|
||||
name: name,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
updateData(type, value, timestamp) {
|
||||
// this.timeArray.push(timestamp);
|
||||
switch (type) {
|
||||
case "Temp":
|
||||
this.tempArray.push(value);
|
||||
break;
|
||||
case "Humi":
|
||||
this.humiArray.push(value);
|
||||
break;
|
||||
case "eCO2":
|
||||
this.eco2Array.push(value / 10);
|
||||
break;
|
||||
case "TVOC":
|
||||
this.tvocArray.push(value / 10);
|
||||
break;
|
||||
default:
|
||||
console.error("Invalid type");
|
||||
}
|
||||
}
|
||||
|
||||
updateGraph() {
|
||||
let update = {
|
||||
x: [this.timeArray],
|
||||
y: [this.tempArray, this.humiArray, this.eco2Array, this.tvocArray],
|
||||
};
|
||||
console.log(update);
|
||||
Plotly.update(this.id, update);
|
||||
}
|
||||
}
|
||||
|
||||
class LiveGraph extends Graph {
|
||||
// Constructor to initialize the graph
|
||||
constructor(id) {
|
||||
super(id);
|
||||
this.tempArray = [];
|
||||
this.humiArray = [];
|
||||
this.eco2Array = [];
|
||||
this.tvocArray = [];
|
||||
this.cnt = 0;
|
||||
}
|
||||
// Function to update the graph with new values got from updateData function
|
||||
updateGraph() {
|
||||
let time = new Date();
|
||||
this.timeArray.push(new Date());
|
||||
|
||||
let update = {
|
||||
x: [[this.timeArray]],
|
||||
y: [
|
||||
[this.tempArray],
|
||||
[this.humiArray],
|
||||
[this.eco2Array],
|
||||
[this.tvocArray],
|
||||
],
|
||||
};
|
||||
|
||||
let olderTime = time.setMinutes(time.getMinutes() - 1);
|
||||
let futureTime = time.setMinutes(time.getMinutes() + 1);
|
||||
let minuteView = {
|
||||
xaxis: {
|
||||
type: "date",
|
||||
range: [olderTime, futureTime],
|
||||
},
|
||||
};
|
||||
Plotly.relayout(this.id, minuteView);
|
||||
if (this.cnt === 10) clearInterval(interval);
|
||||
}
|
||||
// function to get the new data for graph
|
||||
updateData(temperature, humidity, eCO2, TVOC) {
|
||||
// Update the graph
|
||||
this.tempArray.push(temperature);
|
||||
this.humiArray.push(humidity);
|
||||
this.eco2Array.push(eCO2 / 10);
|
||||
this.tvocArray.push(TVOC / 10);
|
||||
}
|
||||
}
|
||||
|
||||
class DataProcessor {
|
||||
constructor(data) {
|
||||
this.data = data;
|
||||
this.graph;
|
||||
}
|
||||
// You can add more filtering methods based on different criteria if needed
|
||||
update(data) {
|
||||
this.data = data;
|
||||
|
||||
console.log("Data updated");
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
makeGraph() {
|
||||
this.graph = new Graph(1);
|
||||
this.graph.makeGraph("temp", "red", "Temperature");
|
||||
this.graph.makeGraph("humi", "blue", "Humidity");
|
||||
this.graph.makeGraph("eco2", "green", "eCO2");
|
||||
this.graph.makeGraph("tvoc", "#F5G644", "TVOC");
|
||||
}
|
||||
|
||||
updateGraph() {
|
||||
this.graph.timeArray = [];
|
||||
this.graph.tempArray = [];
|
||||
this.graph.humiArray = [];
|
||||
this.graph.eco2Array = [];
|
||||
this.graph.tvocArray = [];
|
||||
|
||||
for (let i = 0; i < this.data.length; i++) {
|
||||
if (i % 4 == 0){
|
||||
this.graph.timeArray.push(this.data[i].TimeStamp);
|
||||
}
|
||||
this.graph.updateData(this.data[i].Type, this.data[i].Value, this.data[i].TimeStamp);
|
||||
console.log(this.data[i].Type, this.data[i].Value, this.data[i].TimeStamp);
|
||||
}
|
||||
this.graph.updateGraph();
|
||||
}
|
||||
}
|
145
web/newWebsite/graph-main.js
Normal file
145
web/newWebsite/graph-main.js
Normal file
@@ -0,0 +1,145 @@
|
||||
// Sample data - you can replace this with your actual dataset
|
||||
const data = [];
|
||||
processor = new DataProcessor();
|
||||
let link;
|
||||
|
||||
// Function to create checkbox with label
|
||||
function createCheckBox(id, label) {
|
||||
const checkbox = document.createElement("input");
|
||||
checkbox.setAttribute("type", "checkbox");
|
||||
checkbox.setAttribute("id", id);
|
||||
checkbox.setAttribute("class", "checkbox");
|
||||
|
||||
const checkboxLabel = document.createElement("label");
|
||||
checkboxLabel.setAttribute("for", id);
|
||||
checkboxLabel.textContent = label;
|
||||
|
||||
return { checkbox, checkboxLabel };
|
||||
}
|
||||
|
||||
// Create HTML input elements for user input
|
||||
const container = document.createElement("div");
|
||||
container.setAttribute("class", "container");
|
||||
|
||||
const dataTypesContainer = document.createElement("div");
|
||||
dataTypesContainer.setAttribute("class", "data-types");
|
||||
|
||||
const temperatureCheckbox = createCheckBox("Temp", "Temperature");
|
||||
const humidityCheckbox = createCheckBox("Humi", "Humidity");
|
||||
const eco2Checkbox = createCheckBox("eCO2", "eCO2");
|
||||
const tvocCheckbox = createCheckBox("TVOC", "TVOC");
|
||||
|
||||
dataTypesContainer.appendChild(temperatureCheckbox.checkbox);
|
||||
dataTypesContainer.appendChild(temperatureCheckbox.checkboxLabel);
|
||||
dataTypesContainer.appendChild(humidityCheckbox.checkbox);
|
||||
dataTypesContainer.appendChild(humidityCheckbox.checkboxLabel);
|
||||
dataTypesContainer.appendChild(eco2Checkbox.checkbox);
|
||||
dataTypesContainer.appendChild(eco2Checkbox.checkboxLabel);
|
||||
dataTypesContainer.appendChild(tvocCheckbox.checkbox);
|
||||
dataTypesContainer.appendChild(tvocCheckbox.checkboxLabel);
|
||||
container.appendChild(dataTypesContainer);
|
||||
|
||||
const filterButton = document.createElement("button");
|
||||
filterButton.textContent = "Filter Data";
|
||||
filterButton.setAttribute("class", "filter-button");
|
||||
filterButton.addEventListener("click", () => {
|
||||
const startDate = document.getElementById("start-date").value
|
||||
const endDate = document.getElementById("end-date").value
|
||||
const selectedNodes = document
|
||||
.getElementById("node-input")
|
||||
.value.split(",")
|
||||
.map((node) => node.trim());
|
||||
|
||||
const selectedFields = [];
|
||||
const checkboxes = [
|
||||
temperatureCheckbox,
|
||||
humidityCheckbox,
|
||||
eco2Checkbox,
|
||||
tvocCheckbox
|
||||
];
|
||||
|
||||
checkboxes.forEach((checkbox) => {
|
||||
if (checkbox.checkbox.checked) {
|
||||
selectedFields.push(String(checkbox.checkbox.id));
|
||||
}
|
||||
});
|
||||
|
||||
let selectedFieldsString = selectedFields.map(String);
|
||||
|
||||
let formattedString = '(' + selectedFieldsString.map(item => `'${item}'`).join(', ') + ')';
|
||||
|
||||
const filteredData = [
|
||||
startDate,
|
||||
endDate,
|
||||
selectedNodes,
|
||||
formattedString
|
||||
];
|
||||
|
||||
console.log(filteredData);
|
||||
console.log(startDate, endDate, selectedNodes);
|
||||
|
||||
generateLink(startDate, endDate, selectedNodes, formattedString);
|
||||
fetchData();
|
||||
});
|
||||
|
||||
const dateFilter = document.createElement("div");
|
||||
dateFilter.setAttribute("class", "date-filter");
|
||||
|
||||
const startDateInput = document.createElement("input");
|
||||
startDateInput.setAttribute("type", "datetime-local");
|
||||
startDateInput.setAttribute("id", "start-date");
|
||||
startDateInput.setAttribute("class", "input-field");
|
||||
|
||||
const endDateInput = document.createElement("input");
|
||||
endDateInput.setAttribute("type", "datetime-local");
|
||||
endDateInput.setAttribute("id", "end-date");
|
||||
endDateInput.setAttribute("class", "input-field");
|
||||
|
||||
dateFilter.appendChild(startDateInput);
|
||||
dateFilter.appendChild(endDateInput);
|
||||
container.appendChild(dateFilter);
|
||||
|
||||
const nodeFilter = document.createElement("div");
|
||||
nodeFilter.setAttribute("class", "node-filter");
|
||||
|
||||
const nodeInput = document.createElement("input");
|
||||
nodeInput.setAttribute("type", "text");
|
||||
nodeInput.setAttribute("placeholder", "Enter Node Name (* for all)");
|
||||
nodeInput.setAttribute("id", "node-input");
|
||||
nodeInput.setAttribute("class", "input-field");
|
||||
|
||||
nodeFilter.appendChild(nodeInput);
|
||||
container.appendChild(nodeFilter);
|
||||
|
||||
container.appendChild(filterButton);
|
||||
|
||||
document.body.appendChild(container);
|
||||
|
||||
// Function to get the link for the get request
|
||||
function generateLink(dateStart, dateEnd, node, dataTypes) {
|
||||
const baseUrl = 'http://145.92.8.114/getMeasurements';
|
||||
const formattedDateStart = new Date(dateStart).toISOString().replace('T', '%20');
|
||||
const formattedDateEnd = new Date(dateEnd).toISOString().replace('T', '%20');
|
||||
|
||||
link = `${baseUrl}?dateStart=${formattedDateStart}&dateEnd=${formattedDateEnd}&node=${node}&dataType=${dataTypes}`;
|
||||
|
||||
console.log(link);
|
||||
}
|
||||
processor.makeGraph();
|
||||
// Get request to fetch data from the server
|
||||
function fetchData() {
|
||||
fetch(link)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
processor.update(data);
|
||||
processor.updateGraph();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error fetching data:", error);
|
||||
});
|
||||
}
|
@@ -6,6 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="styles/graph-styles.css">
|
||||
<title>Graphs</title>
|
||||
<script src="https://cdn.plot.ly/plotly-latest.min.js" charset="utf-8"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
|
||||
@@ -22,12 +23,13 @@
|
||||
<li class="nav-item">
|
||||
<a href="settings.html" class="nav-link">Settings</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="questions-dashboard.html" class="nav-link">Questions</a>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
|
||||
<body>
|
||||
<script src="graph-main.js"></script>
|
||||
<script src="graph-classes.js"></script>
|
||||
<script src="graph-main.js"></script>
|
||||
</body>
|
||||
</html>
|
@@ -6,6 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="styles/dashboard-styles.css">
|
||||
<title>Gauges</title>
|
||||
<script src="https://cdn.plot.ly/plotly-latest.min.js" charset="utf-8"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link
|
||||
@@ -24,22 +25,13 @@
|
||||
<li class="nav-item">
|
||||
<a href="settings.html" class="nav-link">Settings</a>
|
||||
</li>
|
||||
<div id="editNodeInformation">
|
||||
<button id="settings" onclick="settings()">Settings</button>
|
||||
</div>
|
||||
<div id="editNode">
|
||||
<textarea id="inputName"></textarea>
|
||||
<textarea id="inputLocation"></textarea>
|
||||
<select id="mySelect"></select>
|
||||
<button id="button" onclick="changeText()">Change Information</button>
|
||||
<p id="text">Status updating</p>
|
||||
</div>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<body>
|
||||
|
||||
<body>
|
||||
<script src="GaugGroup.js"></script>
|
||||
<script src="graph-classes.js"></script>
|
||||
<script src="main.js"></script>
|
||||
<script src="text.js"></script>
|
||||
<script src="liveGraph.js"></script>
|
||||
|
@@ -54,15 +54,16 @@ class liveGraph {
|
||||
}
|
||||
|
||||
// Function to update the graph with new values got from updateData function
|
||||
updateGraph() {
|
||||
updateGraph(temperature, humidity, eCO2, TVOC) {
|
||||
// Update the graph
|
||||
this.tempArray.push(temperature);
|
||||
this.humiArray.push(humidity);
|
||||
this.eco2Array.push(eCO2 / 10);
|
||||
this.tvocArray.push(TVOC / 10);
|
||||
|
||||
let time = new Date();
|
||||
this.timeArray.push(new Date());
|
||||
|
||||
let update = {
|
||||
x: [[this.timeArray]],
|
||||
y: [[this.tempArray], [this.humiArray], [this.eco2Array], [this.tvocArray]]
|
||||
};
|
||||
|
||||
let olderTime = time.setMinutes(time.getMinutes() - 1);
|
||||
let futureTime = time.setMinutes(time.getMinutes() + 1);
|
||||
let minuteView = {
|
||||
@@ -75,11 +76,4 @@ class liveGraph {
|
||||
if (this.cnt === 10) clearInterval(interval);
|
||||
}
|
||||
// function to get the new data for graph
|
||||
updateData(temperature, humidity, eCO2, TVOC) {
|
||||
// Update the graph
|
||||
this.tempArray.push(temperature);
|
||||
this.humiArray.push(humidity);
|
||||
this.eco2Array.push(eCO2 / 10);
|
||||
this.tvocArray.push(TVOC / 10);
|
||||
}
|
||||
}
|
@@ -4,7 +4,7 @@ const sensorData = {};
|
||||
let liveGraphs = [];
|
||||
let nodeArray = [];
|
||||
let nodeDict = {};
|
||||
|
||||
let graphArray = [];
|
||||
// letiables
|
||||
let intervalDelay = 5000;
|
||||
let amountOfNodes = 3;
|
||||
@@ -66,6 +66,7 @@ async function handleIncomingData(data) {
|
||||
sensorData[data.node].updateGauge(2, humidity);
|
||||
sensorData[data.node].updateGauge(3, CO2);
|
||||
sensorData[data.node].updateGauge(4, TVOC);
|
||||
sensorData[data.node].graph.updateGraph(temperature, humidity, CO2, TVOC);
|
||||
} else {
|
||||
console.error('No sensor data for node:', nodeName);
|
||||
}
|
||||
@@ -98,31 +99,14 @@ async function nodeAdressHandler(node, dataTypes) {
|
||||
|
||||
let gaugeGroup = new GaugeGroup(nodeName, nodeLocation, dataTypes.length, maxGaugeValues, dataTypes);
|
||||
sensorData[node] = gaugeGroup;
|
||||
gaugeGroup.graph = new liveGraph(nodeName);
|
||||
sensorData[node] = gaugeGroup;
|
||||
gaugeGroup.graph.makeGraph();
|
||||
sensorData[node] = gaugeGroup;
|
||||
}
|
||||
}
|
||||
|
||||
function createGauge(node, dataType) {
|
||||
// Create a new gauge here
|
||||
let gauge = new GaugeGroup(node, dataType); // Assuming Gauge is the name of the class
|
||||
sensorData[node][dataType] = gauge; // Store the gauge in the sensorData object for later use
|
||||
}
|
||||
|
||||
|
||||
//function for making the html elements for the following html code
|
||||
function nodeData(data) {
|
||||
let nodeData = document.createElement("div");
|
||||
nodeData.innerHTML = data;
|
||||
// nodeData.setAttribute("id", "node" + node);
|
||||
document.body.appendChild(nodeData);
|
||||
// console.log("Hello World");
|
||||
}
|
||||
|
||||
|
||||
function updateGauge(nodeNumber, dataType, value) {
|
||||
// Update the gauge here
|
||||
let gauge = sensorData[nodeNumber][dataType]; // Get the gauge from the sensorData object
|
||||
gauge.update(value); // Assuming the Gauge class has an update method
|
||||
}
|
||||
|
||||
function getNodeInfo(node){
|
||||
return fetch("http://145.92.8.114/getNodeInfo?macAdress=" + node)
|
||||
@@ -142,4 +126,19 @@ function getNodeInfo(node){
|
||||
};
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// create a function to enable and disable the graph using .disabled using the id of the graph
|
||||
function toggleGraph(nodeId) {
|
||||
let graph = document.querySelector('#liveGraph' + nodeId);
|
||||
|
||||
if (graph) {
|
||||
if (graph.classList.contains('disabled')) {
|
||||
graph.classList.remove('disabled');
|
||||
} else {
|
||||
graph.classList.add('disabled');
|
||||
}
|
||||
} else {
|
||||
console.error('No element found with id: liveGraph' + nodeId);
|
||||
}
|
||||
}
|
27
web/newWebsite/questions-chart-class.js
Normal file
27
web/newWebsite/questions-chart-class.js
Normal file
@@ -0,0 +1,27 @@
|
||||
class ChartConfigClass{
|
||||
constructor(data, text){
|
||||
this.data = data
|
||||
this.text = text
|
||||
}
|
||||
|
||||
get chartConfig() {
|
||||
return{
|
||||
type: 'pie',
|
||||
data: this.data,
|
||||
options: {
|
||||
responsive: true,
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: this.text
|
||||
},
|
||||
animation: {
|
||||
animateScale: true,
|
||||
animateRotate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
web/newWebsite/questions-creation-class.js
Normal file
27
web/newWebsite/questions-creation-class.js
Normal file
@@ -0,0 +1,27 @@
|
||||
class QuestionCreationClass {
|
||||
constructor(data, label) {
|
||||
this.data = data;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
get questionData() {
|
||||
return {
|
||||
labels: this.label,
|
||||
datasets: [{
|
||||
label: 'Responses',
|
||||
data: this.data,
|
||||
backgroundColor: [
|
||||
'rgba(255, 99, 132, 0.2)',
|
||||
'rgba(54, 162, 235, 0.2)',
|
||||
'rgba(255, 206, 86, 0.2)'
|
||||
],
|
||||
borderColor: [
|
||||
'rgba(255, 99, 132, 1)',
|
||||
'rgba(54, 162, 235, 1)',
|
||||
'rgba(255, 206, 86, 1)'
|
||||
],
|
||||
borderWidth: 1
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
59
web/newWebsite/questions-dashboard.html
Normal file
59
web/newWebsite/questions-dashboard.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="styles/questions-dashboard-styles.css">
|
||||
<title>Graphs</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
</head>
|
||||
|
||||
<nav class="navbar">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a href="index.html" class="nav-link">Dashboard</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="graphs.html" class="nav-link">Graphs</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="settings.html" class="nav-link">Settings</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="questions-dashboard.html" class="nav-link">Questions</a>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<body>
|
||||
<div class="chart-container">
|
||||
<div class="chart">
|
||||
<h2>Question 1: How clean are the toilets?</h2>
|
||||
<canvas id="chart1"></canvas>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<h2>Question 2: How clean is the study area?</h2>
|
||||
<canvas id="chart2"></canvas>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<h2>Question 3: What do you think of the temperature in the study area?</h2>
|
||||
<canvas id="chart3"></canvas>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<h2>Question 4: How crowded would you say the study area is?</h2>
|
||||
<canvas id="chart4"></canvas>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<h2>Question 5: Is there enough help available on the 5th floor?</h2>
|
||||
<canvas id="chart5"></canvas>
|
||||
</div>
|
||||
<!-- Add more questions and canvas elements as needed -->
|
||||
</div>
|
||||
<script src="questions-chart-class.js"></script>
|
||||
<script src="questions-creation-class.js"></script>
|
||||
<script src="questions-main.js"></script>
|
||||
</body>
|
||||
</html>
|
54
web/newWebsite/questions-main.js
Normal file
54
web/newWebsite/questions-main.js
Normal file
@@ -0,0 +1,54 @@
|
||||
//For now create dummy data to show on the website.
|
||||
let dummydata1 = [40, 30, 20];
|
||||
let questionOptionsDummy1 = ['disgusting','clean', 'fine'];
|
||||
|
||||
let dummydata2 = [25, 35, 40];
|
||||
let questionOptionsDummy2 = ['disgusting', 'clean', 'normal'];
|
||||
|
||||
let dummydata3 = [30, 20, 20];
|
||||
let questionOptionsDummy3 = ['cold', 'perfect', 'hot'];
|
||||
|
||||
let dummydata4 = [30, 20, 20];
|
||||
let questionOptionsDummy4 = ['really crowded','not at all', 'its fine', ];
|
||||
|
||||
let dummydata5 = [30, 20, 20];
|
||||
let questionOptionsDummy5 = ['no','yes', 'decently'];
|
||||
|
||||
//make arrays to store data.
|
||||
let chartConfigArray = [];
|
||||
let textArray = [];
|
||||
|
||||
let questionArray = [];
|
||||
let questionOptionsDummy = [];
|
||||
let dummydata = [];
|
||||
|
||||
//Go along the array's to fetch data, and push this in a class.
|
||||
for (let i = 0; i < 5; i++) {
|
||||
dummydata.push(dummydata1, dummydata2, dummydata3, dummydata4, dummydata5);
|
||||
questionOptionsDummy.push(questionOptionsDummy1, questionOptionsDummy2, questionOptionsDummy3, questionOptionsDummy4, questionOptionsDummy5);
|
||||
|
||||
questionArray.push(new QuestionCreationClass(dummydata[i], questionOptionsDummy[i]));
|
||||
}
|
||||
|
||||
//Go allong another array to also give the class that creates the charts the data collected by the previous array.
|
||||
for (let i = 0; i < 5; i++){
|
||||
textArray.push('Question 1 Responses', 'Question 2 Responses', 'Question 3 Responses', 'Question 4 Responses', 'Question 5 Responses');
|
||||
|
||||
chartConfigArray.push(new ChartConfigClass(questionArray[i].questionData, textArray[i]));
|
||||
}
|
||||
|
||||
// Create the charts
|
||||
const ctx1 = document.getElementById('chart1').getContext('2d');
|
||||
const myChart1 = new Chart(ctx1, chartConfigArray[0].chartConfig);
|
||||
|
||||
const ctx2 = document.getElementById('chart2').getContext('2d');
|
||||
const myChart2 = new Chart(ctx2, chartConfigArray[1].chartConfig);
|
||||
|
||||
const ctx3 = document.getElementById('chart3').getContext('2d');
|
||||
const myChart3 = new Chart(ctx3, chartConfigArray[2].chartConfig);
|
||||
|
||||
const ctx4 = document.getElementById('chart4').getContext('2d');
|
||||
const myChart4 = new Chart(ctx4, chartConfigArray[3].chartConfig);
|
||||
|
||||
const ctx5 = document.getElementById('chart5').getContext('2d');
|
||||
const myChart5 = new Chart(ctx5, chartConfigArray[4].chartConfig);
|
@@ -22,6 +22,8 @@
|
||||
<li class="nav-item">
|
||||
<a href="settings.html" class="nav-link">Settings</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="questions-dashboard.html" class="nav-link">Questions</a>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
|
@@ -42,21 +42,20 @@
|
||||
} */
|
||||
|
||||
body {
|
||||
padding-top: 5vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
margin-top: 8vh;
|
||||
background-color: #f0f0f0;
|
||||
flex-direction: column;
|
||||
background-color: #afafaf;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.gaugeGroup {
|
||||
width: 98vw;
|
||||
height: 20vh;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column; /* Keep as column */
|
||||
justify-content: flex-start;
|
||||
@@ -68,8 +67,8 @@ body {
|
||||
clear: both;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
float: top;
|
||||
}
|
||||
|
||||
.groupTitle {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
@@ -112,7 +111,9 @@ body {
|
||||
height: 80%; /* Increase the height from 70% to 80% */
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
|
||||
height: 5vh;
|
||||
margin-bottom: 1vh;
|
||||
|
||||
}
|
||||
|
||||
.gaugeImage {
|
||||
@@ -125,7 +126,6 @@ body {
|
||||
left: 0;
|
||||
z-index: 1; /* Make the image display below the needle */
|
||||
bottom: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.gaugeValue, .gaugeText {
|
||||
@@ -215,4 +215,24 @@ body {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.plotly-container {
|
||||
width: 100%;
|
||||
float: bottom;
|
||||
padding: 1vw;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.js-plotly-plot {
|
||||
width: 90%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
|
||||
}
|
@@ -1,168 +1,116 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: "Roboto", sans-serif;
|
||||
box-sizing: border-box;
|
||||
font-family: "Roboto", sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
padding-top: 5vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f0f0f0;
|
||||
flex-direction: column;
|
||||
background-color: #afafaf;
|
||||
|
||||
}
|
||||
|
||||
.gaugeGroup {
|
||||
width: 98vw;
|
||||
height: 20vh;
|
||||
display: flex;
|
||||
flex-direction: column; /* Keep as column */
|
||||
justify-content: flex-start;
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
padding: 10px;
|
||||
border-radius: 50px;
|
||||
border: 2px solid #333;
|
||||
clear: both;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
float: top;
|
||||
}
|
||||
.groupTitle {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.Node {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.Sensorvalues {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
width: 15%;
|
||||
height: 110%;
|
||||
background-color: #ddd;
|
||||
color: #333;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
padding-bottom: 6vh; /* Increase bottom padding */
|
||||
|
||||
}
|
||||
|
||||
.gaugeContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 80%; /* Increase the height from 70% to 80% */
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
|
||||
}
|
||||
|
||||
.gaugeImage {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 200%;
|
||||
object-fit: contain;
|
||||
position: absolute; /* Make the image position absolute */
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1; /* Make the image display below the needle */
|
||||
bottom: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.gaugeValue, .gaugeText {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.gaugeText {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 1.4vw;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
bottom: -3.2vw; /* Adjust this value to move the text further down */
|
||||
|
||||
}
|
||||
|
||||
|
||||
.valueContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#valueText {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.needle {
|
||||
position: absolute;
|
||||
bottom: -40%; /* Lower the needle to the bottom */
|
||||
left: 50%;
|
||||
width: 2px;
|
||||
height: 110%;
|
||||
background-color: black;
|
||||
transform-origin: bottom;
|
||||
z-index: 3; /* Make the needle display above the image */
|
||||
}
|
||||
|
||||
.contentContainer {
|
||||
display: flex;
|
||||
flex-direction: row; /* Layout children side by side */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-top: 5vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #afafaf;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: #333;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
position: fixed; /* Fix the navbar at the top of the page */
|
||||
top: 0; /* Position it at the very top */
|
||||
width: 100%; /* Make it span the full width of the page */
|
||||
z-index: 1000; /* Make sure it's above all other elements */
|
||||
background-color: #333;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
position: fixed; /* Fix the navbar at the top of the page */
|
||||
top: 0; /* Position it at the very top */
|
||||
width: 100%; /* Make it span the full width of the page */
|
||||
z-index: 1000; /* Make sure it's above all other elements */
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center; /* Center the links horizontally */
|
||||
height: 100%;
|
||||
width: 100%; /* Make it span the full width of the navbar */
|
||||
list-style: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center; /* Center the links horizontally */
|
||||
height: 100%;
|
||||
width: 100%; /* Make it span the full width of the navbar */
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin-right: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
font-size: 18px;
|
||||
}
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border: 2px solid #ccc;
|
||||
border-radius: 10px;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
width: 90;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.data-types {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-right: 60px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 0.8em;
|
||||
padding: 8px;
|
||||
border-radius: 5px;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.date-filter {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
margin-right: 60px;
|
||||
font-size: 1.2em; /* Increase font size for a bigger appearance */
|
||||
padding: 8px;
|
||||
border-radius: 5px; /* Add border radius for a rounded look */
|
||||
background-color: #f0f0f0; /* Light background color for contrast */
|
||||
}
|
||||
|
||||
.node-filter {
|
||||
flex: 1;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
margin-right: 60px;
|
||||
align-content: center;
|
||||
font-size: 1.2em; /* Increase font size for a bigger appearance */
|
||||
padding: 8px;
|
||||
border-radius: 5px; /* Add border radius for a rounded look */
|
||||
background-color: #f0f0f0; /* Light background color for contrast */
|
||||
}
|
||||
|
||||
.filter-button {
|
||||
width: 10%;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.js-plotly-plot {
|
||||
align-self: center;
|
||||
width: 95%;
|
||||
height: 100%;
|
||||
margin: 10px;
|
||||
}
|
||||
/* Additional styling as needed */
|
||||
|
70
web/newWebsite/styles/questions-dashboard-styles.css
Normal file
70
web/newWebsite/styles/questions-dashboard-styles.css
Normal file
@@ -0,0 +1,70 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: "Roboto", sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
flex-direction: row;
|
||||
padding-top: 5vw;
|
||||
display: flex;
|
||||
background-color: #dbd7d7;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: #3d898c;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center ;
|
||||
padding: 0 20px;
|
||||
position: fixed; /* Fix the navbar at the top of the page */
|
||||
top: 0; /* Position it at the very top */
|
||||
width: 100%; /* Make it span the full width of the page */
|
||||
z-index: 1000; /* Make sure it's above all other elements */
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center; /* Center the links horizontally */
|
||||
height: 100%;
|
||||
width: 100%; /* Make it span the full width of the navbar */
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#data-container {
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
justify-content: center;
|
||||
align-self: start;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 30%;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
background-color: #dbd7d7;
|
||||
border: 3px solid #000;
|
||||
border-radius: 30px;
|
||||
}
|
||||
canvas {
|
||||
margin-bottom: 20px;
|
||||
}
|
@@ -0,0 +1,202 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: "Roboto", sans-serif;
|
||||
|
||||
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
margin-top: 8vh;
|
||||
background-color: #f0f0f0;
|
||||
flex-direction: column;
|
||||
background-color: #afafaf;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.gaugeGroup {
|
||||
width: 98vw;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column; /* Keep as column */
|
||||
justify-content: flex-start;
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
padding: 10px;
|
||||
border-radius: 50px;
|
||||
border: 2px solid #333;
|
||||
clear: both;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.groupTitle {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.Node {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.Sensorvalues {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
width: 15%;
|
||||
height: 110%;
|
||||
background-color: #ddd;
|
||||
color: #333;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
padding-top: 3vh; /* Increase bottom padding */
|
||||
|
||||
}
|
||||
|
||||
.gaugeContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 80%; /* Increase the height from 70% to 80% */
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
height: 5vh;
|
||||
margin-bottom: 1vh;
|
||||
|
||||
}
|
||||
|
||||
.gaugeImage {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 120%;
|
||||
object-fit: contain;
|
||||
position: absolute; /* Make the image position absolute */
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1; /* Make the image display below the needle */
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.gaugeValue, .gaugeText {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.gaugeText {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 1.4vw;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
top: -1.4vw; /* Adjust this value to move the text further down */
|
||||
|
||||
}
|
||||
|
||||
.arrowimg {
|
||||
width: 3vh;
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
position: absolute;
|
||||
top: 0.5vw;
|
||||
right: 1.2vw;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.valueContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#valueText {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.needle {
|
||||
position: absolute;
|
||||
bottom: -10%; /* Lower the needle to the bottom */
|
||||
left: 50%;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
background-color: black;
|
||||
transform-origin: bottom;
|
||||
z-index: 3; /* Make the needle display above the image */
|
||||
transition: transform 0.1s ease-out;
|
||||
|
||||
}
|
||||
|
||||
.contentContainer {
|
||||
display: flex;
|
||||
flex-direction: row; /* Layout children side by side */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: #333;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
position: fixed; /* Fix the navbar at the top of the page */
|
||||
top: 0; /* Position it at the very top */
|
||||
width: 100%; /* Make it span the full width of the page */
|
||||
z-index: 1000; /* Make sure it's above all other elements */
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center; /* Center the links horizontally */
|
||||
height: 100%;
|
||||
width: 100%; /* Make it span the full width of the navbar */
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.plotly-container {
|
||||
width: 100%;
|
||||
float: bottom;
|
||||
padding: 1vw;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.js-plotly-plot {
|
||||
width: 90%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
|
||||
}
|
Reference in New Issue
Block a user