TimberVis Flexiboard: Visualization and Exploration Flexiboard for Timber Buildings IoT data sensors.
The pulse of the wood
Monitoring of Wooden houses: Time series of sensors data measuring
humidity, temperatures, vibrations and weather conditions.
https://lnu.se/forskning/forskningsprojekt/projekt-flexiboard-for-visualisering-och-utforskning-av-trabyggnader/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2189 lines
74 KiB
2189 lines
74 KiB
|
|
/////////////////////////////////////////////
|
|
// Check if house var is selected: if not then go back to home.html
|
|
// if yesm then load default empty dashboard for the selected house
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const myParam = urlParams.get('h');
|
|
|
|
var currentURL = window.location.href;
|
|
|
|
// Extract the HTML file name
|
|
var fileName = currentURL.substring(currentURL.lastIndexOf('/') + 1);
|
|
|
|
// Get the list of projects
|
|
var count = 0; var myNumber = 0;
|
|
const projectList = [];
|
|
PROJECTS.items.forEach((houses) => {
|
|
projectList.push(houses.xname);
|
|
// get the number/order of the config selected element
|
|
if (houses.xname == myParam) {myNumber = count;}
|
|
count++;
|
|
});
|
|
|
|
////////////////////////////////////////////////////
|
|
////// GO BACK OR CONTINUE IN DASHBOARD:
|
|
if (!myParam || !projectList.includes(myParam)) {
|
|
alert("Välj ett projekt från hemsidan.\nPlease choose a project from the home page.")
|
|
window.location.replace('home.html');
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////
|
|
// DEFAULT INTERFACE BUILDING
|
|
var myHouse = PROJECTS.items[myNumber];
|
|
var myHouseXname = PROJECTS.items[myNumber].xname;
|
|
//cl(myHouseXname);
|
|
document.getElementById("h1_title").innerHTML = h1_title;
|
|
document.getElementById("subtitle").innerHTML = subtitle;
|
|
|
|
|
|
/////////////////////////////////
|
|
// Get and apply the range of time for the data.
|
|
|
|
|
|
//////////////////////////////////
|
|
// LIST OF SENSOR NAME and Families.
|
|
// Get the list of sensors, amnd the range_years_month from config/datamap.js
|
|
var projectName = myParam;
|
|
var listSensors = null;
|
|
var range_year_month;
|
|
for (var i = 0; i < PROJECTS.items.length; i++) {
|
|
if (PROJECTS.items[i].xname === projectName) {
|
|
range_year_month = PROJECTS.items[i].range_years_month;
|
|
listSensors = PROJECTS.items[i].listOfSensors;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Arrange the range_years_month
|
|
var first_year = parseInt(range_year_month[0].split("-")[0], 10);
|
|
var first_year_month = range_year_month[0];
|
|
var last_year = parseInt(range_year_month[1].split("-")[0], 10);
|
|
var last_year_month = range_year_month[1];
|
|
|
|
// Creta select option according to time range:
|
|
listYearOptions = "";
|
|
for (var i = last_year; i >= first_year; i--) {
|
|
listYearOptions += `<option value="${i}">${i}</option>`;
|
|
}
|
|
|
|
// Get unique list of families
|
|
var listParametersRepeated = [];
|
|
// Get families anmes and sensors select optionssensors
|
|
var listSensorsOptions = "";
|
|
var listFamiliesRepeated = [];
|
|
for (var i = 0; i < listSensors.length; i++) {
|
|
listFamiliesRepeated.push(listSensors[i].split(".")[0]);
|
|
listParametersRepeated.push(listSensors[i].split(".")[2]);
|
|
listSensorsOptions += `<option value="${listSensors[i].split(".")[1]}">${listSensors[i].split(".")[1]}</option>`;
|
|
}
|
|
var listFamiliesRepeated2 = new Set(listFamiliesRepeated);
|
|
var listFamilies = Array.from(listFamiliesRepeated2);
|
|
var listFamiliesOptions = "";
|
|
// Get fammily select options
|
|
for (var i = 0; i < listFamilies.length; i++) {
|
|
listFamiliesOptions += `<option value="${listFamilies[i]}">${listFamilies[i]}</option>`;
|
|
}
|
|
var listParametersRepeated2 = new Set(listParametersRepeated);
|
|
var listParameters = Array.from(listParametersRepeated2);
|
|
var listParametersOptions = "";
|
|
// Get parameters select options
|
|
for (var i = 0; i < listParameters.length; i++) {
|
|
listParametersOptions += `<option value="${listParameters[i]}">${listParameters[i]}</option>`;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////
|
|
// ADDING HTML dynamic pieces
|
|
|
|
// Adding HTML navbar
|
|
var HTML_navBar = `
|
|
<div class="container-fluid">
|
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarTogglerDemo01" aria-controls="navbarTogglerDemo01" aria-expanded="false" aria-label="Toggle navigation">
|
|
<span class="navbar-toggler-icon"></span>
|
|
</button>
|
|
<div class="collapse navbar-collapse" id="navbarTogglerDemo01">
|
|
<a class="navbar-brand" href="`+homePagageUrl+`">
|
|
<img src="imgs/infravis.jpg" alt="" width="30" height="24" class="d-inline-block align-text-top">
|
|
TräHus
|
|
</a>
|
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
|
<li class="nav-item">
|
|
<a class="nav-link active" aria-current="page" href="`+homePagageUrl+`">Home</a>
|
|
</li>
|
|
</ul>
|
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
|
<li class="nav-item">
|
|
<a class="nav-link" href="http://infravis.se" target="_infravis_se_site" aria-current="page">Powered by Infravis</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById("navbar").innerHTML = HTML_navBar;
|
|
|
|
// AddingSELECT OPTIONS for:
|
|
// Time range:
|
|
document.getElementById("PYYear").innerHTML = listYearOptions
|
|
document.getElementById("GYYear").innerHTML = listYearOptions
|
|
document.getElementById("HYYear").innerHTML = listYearOptions
|
|
|
|
// time range of data
|
|
document.getElementById("PYYear").innerHTML = listYearOptions;
|
|
document.getElementById("GYYear").innerHTML = listYearOptions;
|
|
document.getElementById("HYYear").innerHTML = listYearOptions;
|
|
|
|
// Families:
|
|
document.getElementById("PYFamSensor").innerHTML = listFamiliesOptions;
|
|
document.getElementById("PMFamSensor").innerHTML = listFamiliesOptions;
|
|
document.getElementById("PWFamSensor").innerHTML = listFamiliesOptions;
|
|
document.getElementById("PDFamSensor").innerHTML = listFamiliesOptions;
|
|
|
|
document.getElementById("HDFamSensor").innerHTML = listFamiliesOptions;
|
|
document.getElementById("HWFamSensor").innerHTML = listFamiliesOptions;
|
|
document.getElementById("HMFamSensor").innerHTML = listFamiliesOptions;
|
|
document.getElementById("HYFamSensor").innerHTML = listFamiliesOptions;
|
|
|
|
// Sensors:
|
|
document.getElementById("GYSensor").innerHTML = listSensorsOptions;
|
|
document.getElementById("GMSensor").innerHTML = listSensorsOptions;
|
|
document.getElementById("GWSensor").innerHTML = listSensorsOptions;
|
|
|
|
// Type:
|
|
document.getElementById("PYParameter").innerHTML = listParametersOptions;
|
|
document.getElementById("PMParameter").innerHTML = listParametersOptions;
|
|
document.getElementById("PWParameter").innerHTML = listParametersOptions;
|
|
document.getElementById("PDParameter").innerHTML = listParametersOptions;
|
|
|
|
document.getElementById("GYParameter").innerHTML = listParametersOptions;
|
|
document.getElementById("GMParameter").innerHTML = listParametersOptions;
|
|
document.getElementById("GWParameter").innerHTML = listParametersOptions;
|
|
|
|
document.getElementById("HYParameter").innerHTML = listParametersOptions;
|
|
document.getElementById("HMParameter").innerHTML = listParametersOptions;
|
|
document.getElementById("HWParameter").innerHTML = listParametersOptions;
|
|
document.getElementById("HDParameter").innerHTML = listParametersOptions;
|
|
|
|
|
|
|
|
// house info card:
|
|
var card = `
|
|
<h3 class="${myHouse.name}">${myHouse.hname}</h3>
|
|
<div class="d-flex">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<p>${myHouse.subtitle}</p>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<a href="${myHouse.image}" target="picture of ${myHouse.hname}"><img src="${myHouse.image}" class="card-img-top img-fluid imgshadow" alt="..."></a>
|
|
<a href="${myHouse.map_url}" target="map of ${myHouse.hname}"><img src="${myHouse.map}" class="card-img-top img-fluid imgshadow" alt="..."></<>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
|
|
document.getElementById("myhouse").innerHTML = card;
|
|
|
|
|
|
//-----------------------------
|
|
// writeIn and writeInTXT
|
|
|
|
async function writeIn(formId, formData) {//, apiUrl, myid) {
|
|
var myColumn = getName(formId[0])
|
|
var myTitle = document.getElementById(formId[0]+"Text").value;
|
|
|
|
// cl("formId[0]: "+formId[0]);
|
|
// cl("myColumn: "+myColumn);
|
|
switch(myColumn) {
|
|
case "col-left":
|
|
// cl("formData from writeIn():"+formData);
|
|
var myid = "parallel-"+Math.floor(Math.random() * 9999999);
|
|
await createMyDiv(myid, myColumn, myTitle);
|
|
createParallel(myid, formId, formData);
|
|
break;
|
|
case "col-center":
|
|
var myid = "grid-"+Math.floor(Math.random() * 1000000);
|
|
await createMyDiv(myid, myColumn, myTitle);
|
|
createGrid(myid, formId,formData);
|
|
break;
|
|
case "col-right":
|
|
var myid = "horizon-"+Math.floor(Math.random() * 1000000);
|
|
await createMyDiv(myid, myColumn, myTitle);
|
|
createHorizon(myid, formId, formData);
|
|
}
|
|
|
|
}
|
|
|
|
async function writeInTXT(formId, textData) {
|
|
var myColumn = getName(formId[0])
|
|
var myid = formId[0]+"-txt-"+Math.floor(Math.random() * 1000000);
|
|
await createMyDiv(myid, myColumn, "");
|
|
createTXT(myid, textData);
|
|
}
|
|
|
|
//-----------------------------
|
|
// CreateDIV
|
|
async function createMyDiv(myid, myColumn, myTitle) {
|
|
var buttonOpenPopup = [];
|
|
buttonOpenPopup["col-left"] = '<span class="corner-popup" onclick="openPopup(this)"><i class="fa fa-window-maximize"></i></span>';
|
|
buttonOpenPopup["col-center"] = '<span class="corner-popup" onclick="openPopup(this)"><i class="fa fa-window-maximize"></i></span>';
|
|
buttonOpenPopup["col-right"] = '<span class="corner-popup" onclick="openPopupCanvas(this)"><i class="fa fa-window-maximize"></i></span>';
|
|
|
|
myButtons ='<span class="corner-close" onclick="removeDiv(this)"><i class="fa fa-close"></i></span>'+buttonOpenPopup[myColumn]; //+'<span class="corner-print" onclick="printWindow(this)"><i class="fa fa-print"></i></span>';
|
|
if(!myTitle=="") {typeof myTitle === 'undefined' ? myTitle = "":myTitle = "<h2>"+myTitle+"</h2>";}
|
|
|
|
node = document.createElement("div");
|
|
node.setAttribute("id", myid);
|
|
if (myColumn == "col-center") {
|
|
node.setAttribute("class", "mysvg sortable-item");
|
|
} else {
|
|
node.setAttribute("class", "sortable-item");
|
|
}
|
|
document.getElementById(myColumn).appendChild(node);
|
|
document.getElementById(myid).innerHTML = myButtons+myTitle;
|
|
}
|
|
|
|
//-----------------------------
|
|
// createParallel
|
|
async function createParallel(myid,formId, myFormData) {
|
|
const myForm = document.getElementById(formId);
|
|
switch (formId) {
|
|
case "PD":
|
|
var inDay = document.getElementById("PDYearMonthDay").value;
|
|
var parts = inDay.split("/");
|
|
var day = `${parts[2]}-${parts[1]}-${parts[0]}`;
|
|
var myTitle = document.getElementById("PDTitle").value;
|
|
var family = document.getElementById("PDFamSensor").value;
|
|
var type = document.getElementById("PDParameter").value;
|
|
// Building API query
|
|
var apiUrl = "parallel/daily?hus="+myHouseXname+"&family="+family+"&type="+type+"&day="+day;
|
|
makeApiRequest(formId, apiUrl, myid, myTitle, family, type, day);
|
|
|
|
break;
|
|
//-------------------------------
|
|
case "PW":
|
|
var inWeek = document.getElementById("PWWeekMonday").value;
|
|
var week = getWeekNumberFromDate(inWeek);
|
|
var inYear = inWeek.split("/");
|
|
var year = inYear[2];
|
|
var family = document.getElementById("PWFamSensor").value;
|
|
var myTitle = document.getElementById("PWTitle").value;
|
|
var type = document.getElementById("PWParameter").value;
|
|
// BUILDING API query
|
|
var apiUrl = "parallel/weekly?hus="+myHouseXname+"&family="+family+"&type="+type+"&year="+year+"&week="+week;
|
|
makeApiRequest(formId, apiUrl, myid, myTitle, family, type, year, week);
|
|
|
|
break;
|
|
//-------------------------------
|
|
case "PM":
|
|
var inMonthYear = document.getElementById("PMYearMonth").value;
|
|
var parts = inMonthYear.split('/');
|
|
var month = parseInt(parts[0], 10); // Parse the month as an integer
|
|
var year = parseInt(parts[1], 10); // Parse the year as an integer
|
|
var family = document.getElementById("PMFamSensor").value;
|
|
var myTitle = document.getElementById("PMTitle").value;
|
|
var type = document.getElementById("PMParameter").value;
|
|
// BUILDING API query
|
|
var apiUrl = "parallel/monthly?hus="+myHouseXname+"&family="+family+"&type="+type+"&year="+year+"&month="+month;
|
|
makeApiRequest(formId, apiUrl, myid, myTitle, family, type, year, month)
|
|
|
|
break;
|
|
//-------------------------------
|
|
case "PY":
|
|
var year = document.getElementById("PYYear").value;
|
|
var family = document.getElementById("PYFamSensor").value;
|
|
var myTitle = document.getElementById("PYTitle").value;
|
|
var type = document.getElementById("PYParameter").value;
|
|
// BUILDING API query
|
|
var apiUrl = "parallel/yearly?hus="+myHouseXname+"&family="+family+"&type="+type+"&year="+year;
|
|
makeApiRequest(formId, apiUrl, myid, myTitle, family, type, year)
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------
|
|
// createGrid
|
|
async function createGrid(myid,formId, myFormData) {
|
|
const myForm = document.getElementById(formId);
|
|
switch (formId) {
|
|
case "GW":
|
|
var myTitle = document.getElementById("GWTitle").value;
|
|
var inDate = document.getElementById("GWYearMonthMonday").value;
|
|
var week = getWeekNumberFromDate(inDate);
|
|
var inYear = inDate.split("/");
|
|
var year = inYear[2];;
|
|
var sensor = document.getElementById("GWSensor").value;
|
|
var type = document.getElementById("GWParameter").value;
|
|
// BUILDING API query
|
|
var apiUrl = "grid/weekly?hus="+myHouseXname+"&sensor="+sensor+"&type="+type+"&year="+year+"&week="+week;
|
|
makeApiRequest(formId, apiUrl, myid, myTitle, sensor, type, inDate);
|
|
break;
|
|
//-------------------------------
|
|
case "GM":
|
|
var myTitle = document.getElementById("GMTitle").value;
|
|
var yearMonth = document.getElementById("GMYearMonth").value;
|
|
var month = yearMonth.split("/")[0];
|
|
var year = yearMonth.split("/")[1];
|
|
var sensor = document.getElementById("GMSensor").value;
|
|
var type = document.getElementById("GMParameter").value;
|
|
// BUILDING API query
|
|
var apiUrl = "grid/monthly?hus="+myHouseXname+"&sensor="+sensor+"&type="+type+"&year="+year+"&month="+month;
|
|
makeApiRequest(formId, apiUrl, myid, myTitle, sensor, type, year, month);
|
|
|
|
break;
|
|
//-------------------------------
|
|
case "GY":
|
|
var myTitle = document.getElementById("GYTitle").value;
|
|
var year = document.getElementById("GYYear").value;
|
|
var sensor = document.getElementById("GYSensor").value;
|
|
var type = document.getElementById("GYParameter").value;
|
|
var apiUrl = "grid/yearly?hus="+myHouseXname+"&sensor="+sensor+"&type="+type+"&year="+year;
|
|
makeApiRequest(formId, apiUrl, myid, myTitle, sensor, type, year);
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
//-----------------------------
|
|
// createHorizon
|
|
async function createHorizon(myid, formId, myFormData) {
|
|
|
|
switch(formId) {
|
|
//-------------------------------
|
|
case "HD":
|
|
|
|
var myTitle = document.getElementById("HYTitle").value;
|
|
|
|
var inDay = document.getElementById("HDYearMonthDay").value;
|
|
var parts = inDay.split("/");
|
|
var day = `${parts[2]}-${parts[1]}-${parts[0]}`;
|
|
|
|
var year = document.getElementById("HYYear").value;
|
|
var family = document.getElementById("HWFamSensor").value;
|
|
var type = document.getElementById("HYParameter").value;
|
|
// BUILDING API query
|
|
var apiUrl = "horizon/daily?hus="+myHouseXname+"&family="+family+"&type="+type+"&day="+day;
|
|
makeApiRequest(formId, apiUrl, myid, myTitle, family, type, year);
|
|
|
|
break;
|
|
//-------------------------------
|
|
case "HW":
|
|
|
|
var myTitle = document.getElementById("HWTitle").value;
|
|
|
|
var inWeek = document.getElementById("HWWeek").value;
|
|
var week = getWeekNumberFromDate(inWeek);
|
|
var inYear = inWeek.split("/");
|
|
var year = inYear[2];
|
|
var family = document.getElementById("HWFamSensor").value;
|
|
var type = document.getElementById("HWParameter").value;
|
|
// BUILDING API query
|
|
var apiUrl = "horizon/weekly?hus="+myHouseXname+"&family="+family+"&type="+type+"&year="+year+"&week="+week;
|
|
makeApiRequest(formId, apiUrl, myid, myTitle, family, type, year, week);
|
|
|
|
break;
|
|
//-------------------------------
|
|
case "HM":
|
|
var title = document.getElementById("HMTitle").value;
|
|
var inYearMonth = document.getElementById("HMYearMonth").value;
|
|
year = inYearMonth.split("/")[1];
|
|
month = parseInt(inYearMonth.split("/")[0]);
|
|
var family = document.getElementById("HMFamSensor").value;
|
|
var type = document.getElementById("HMParameter").value;
|
|
// BUILDING API query
|
|
var apiUrl = "horizon/monthly?hus="+myHouseXname+"&family="+family+"&type="+type+"&year="+year+"&month="+month;
|
|
makeApiRequest(formId, apiUrl, myid, myTitle, family, type, year, month);
|
|
|
|
|
|
break;
|
|
//-------------------------------
|
|
case "HY":
|
|
var myTitle = document.getElementById("HYTitle").value;
|
|
var year = document.getElementById("HYYear").value;
|
|
var family = document.getElementById("HYFamSensor").value;
|
|
var type = document.getElementById("HYParameter").value;
|
|
// BUILDING API query
|
|
var apiUrl = "horizon/yearly?hus="+myHouseXname+"&family="+family+"&type="+type+"&year="+year;
|
|
makeApiRequest(formId, apiUrl, myid, myTitle, family, type, year);
|
|
|
|
|
|
break;
|
|
//-------------------------------
|
|
|
|
}
|
|
}
|
|
|
|
//-----------------------------
|
|
// createTXT
|
|
async function createTXT(myid, textData) {
|
|
var myDiv = document.getElementById(myid);
|
|
myDiv.insertAdjacentHTML('beforeend', textData);
|
|
// Adding argument:
|
|
myDiv.setAttribute("apiurl", "txt");
|
|
}
|
|
|
|
|
|
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//// Final Diagram builders
|
|
|
|
|
|
// Parallel
|
|
function do_parallel(myid, myxs, mycolumns, inMyTitle) {
|
|
//cl(myxs);
|
|
c3.generate({
|
|
bindto: myid,
|
|
data: {
|
|
xs: myxs,
|
|
columns: mycolumns
|
|
},
|
|
legend: {
|
|
hide: true
|
|
//or hide: 'data1'
|
|
//or hide: ['data1', 'data2']
|
|
},
|
|
grid: {
|
|
x: {
|
|
lines: [
|
|
{value: 5, text: 'layer 1'},
|
|
{value: 10, text: 'layer 2'},
|
|
{value: 25, text: 'layer 3'},
|
|
{value: 35, text: 'layer 4'},
|
|
{value: 42, text: 'layer 5'},
|
|
{value: 45, text: 'layer 6'}
|
|
// optional: , position: 'middle', 'start'
|
|
]
|
|
}
|
|
}
|
|
// ,
|
|
// regions: [
|
|
// {axis: 'x', start: 5, end: 10},
|
|
// {axis: 'x', start: 25, end: 35},
|
|
// {axis: 'x', start: 42, end: 45} ////, class: 'regionY'} // start => 2014-01-25 00:00:00, end => 2014-01-30 00:00:00
|
|
// ]
|
|
});
|
|
myButtons =`
|
|
<span class="corner-close" onclick="removeDiv(this)"><i class="fa fa-close"></i></span>
|
|
<span class="corner-popup" onclick="openPopup(this)"><i class="fa fa-window-maximize"></i></span>
|
|
`;
|
|
var myTitle = "<h2>"+inMyTitle+"</h2>";
|
|
my = myid.replace('#', '')
|
|
var mainDiv = document.getElementById(my);
|
|
|
|
mainDiv.insertAdjacentHTML("afterbegin", myButtons+myTitle);
|
|
|
|
}
|
|
|
|
|
|
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
|
|
|
|
//----------------------------------
|
|
// GRID WEEKLY d3 diagram
|
|
function do_grid_weekly(myid, startingDate, data) {
|
|
// Parse the startingDate in DD-MM-YYYY format
|
|
const [day, month, year] = startingDate.split("/").map(Number);
|
|
const startDate = new Date(year, month - 1, day); // Month is 0-indexed
|
|
|
|
// Define the SVG dimensions
|
|
const svgWidth = 260; // Increased width
|
|
const svgHeight = 200; // Increased height
|
|
|
|
// Create an SVG element inside the specified div
|
|
const svg = d3.select(`#${myid}`)
|
|
.append("svg")
|
|
.attr("width", svgWidth)
|
|
.attr("height", svgHeight);
|
|
|
|
// Convert data values to numbers
|
|
const numericData = data.map(value => parseFloat(value));
|
|
|
|
// Define the color scale
|
|
const colorScale = d3.scaleLinear()
|
|
.domain([d3.min(numericData), d3.max(numericData)])
|
|
.range(["#0571b0", "#ca0020"]); // Replace with your desired color range
|
|
|
|
// Calculate the dates for each day of the week starting from Monday
|
|
const daysOfWeek = [];
|
|
const dates = [];
|
|
for (let i = 0; i < 7; i++) {
|
|
const currentDate = new Date(startDate);
|
|
currentDate.setDate(startDate.getDate() + i);
|
|
const day = currentDate.getDate().toString().padStart(2, "0");
|
|
const month = (currentDate.getMonth() + 1).toString().padStart(2, "0");
|
|
daysOfWeek.push(currentDate.toLocaleDateString("en-US", { weekday: "short" }));
|
|
dates.push(`${day}/${month}`);
|
|
}
|
|
|
|
// Year at the Top
|
|
const yearText = `${year}`;
|
|
svg.append("text")
|
|
.attr("x", svgWidth / 2) // Centered horizontally
|
|
.attr("y", 20) // Adjusted y position
|
|
.attr("font-size", "16")
|
|
.attr("text-anchor", "middle") // Centered horizontally
|
|
.text(yearText);
|
|
|
|
// Days of the Week in the First Column (wider)
|
|
svg.selectAll("text.day-label")
|
|
.data(daysOfWeek)
|
|
.enter()
|
|
.append("text")
|
|
.attr("class", "day-label")
|
|
.attr("x", 15) // Increased x position for wider columns
|
|
.attr("y", (d, i) => 40 + i * 20) // Adjusted y position
|
|
.attr("font-size", 10)
|
|
.attr("font-weight", "bold")
|
|
.attr("text-anchor", "middle")
|
|
.text(d => d);
|
|
|
|
// Dates in the Second Column (between Weekday and Square, wider)
|
|
svg.selectAll("text.date-label")
|
|
.data(dates)
|
|
.enter()
|
|
.append("text")
|
|
.attr("class", "date-label")
|
|
.attr("x", 70) // Increased x position for wider columns
|
|
.attr("y", (d, i) => 40 + i * 20) // Adjusted y position
|
|
.attr("font-size", 10)
|
|
.attr("text-anchor", "start")
|
|
.text(d => d);
|
|
|
|
// Squares in the Third Column with Tooltip
|
|
svg.selectAll("rect.square")
|
|
.data(data)
|
|
.enter()
|
|
.append("rect")
|
|
.attr("class", "square")
|
|
.attr("x", 120) // Adjusted x position for squares
|
|
.attr("y", (d, i) => 30 + i * 20) // Adjusted y position
|
|
.attr("width", d => d) // Set the width based on data values
|
|
.attr("height", 10)
|
|
.attr("fill", (d) => colorScale(d)) // Apply color scale
|
|
.on("mouseover", function (d) {
|
|
// Show tooltip with data value
|
|
const [x, y] = d3.mouse(this);
|
|
const tooltip = d3.select("#tooltip");
|
|
tooltip.transition().duration(200).style("opacity", 0.9);
|
|
tooltip.html(`Value: ${d}`)
|
|
// .style("left", (x + 10) + "px")
|
|
// .style("top", (y - 10) + "px");
|
|
.style("left", (d3.event.pageX + 10) + "px")
|
|
.style("top", (d3.event.pageY - 10) + "px")
|
|
.style("background-color", "#c3c3c3")
|
|
.style("opacity", 1)
|
|
.html("Value: " + d.toFixed(2));
|
|
})
|
|
.on("mouseout", () => {
|
|
// Hide tooltip on mouseout
|
|
const tooltip = d3.select("#tooltip");
|
|
tooltip.transition().duration(500).style("opacity", 0);
|
|
});
|
|
|
|
// Tooltip container
|
|
d3.select(`#${myid}`)
|
|
.append("div")
|
|
.attr("id", "tooltip")
|
|
.style("opacity", 0)
|
|
.style("position", "absolute")
|
|
.style("background-color", "white")
|
|
.style("border", "1px solid #ccc")
|
|
.style("padding", "5px");
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//---------------------------------------------------////////
|
|
|
|
|
|
// GRID MONTHLY d3 diagram
|
|
|
|
function getMonth(monthNumber) {
|
|
const date = d3.timeParse("%m")(monthNumber);
|
|
const monthName = d3.timeFormat("%B")(date);
|
|
return monthName;
|
|
}
|
|
|
|
function do_grid_monthly(myid, month, year, data, apiUrl) {
|
|
const margin = { top: 60, right: 20, bottom: 20, left: 20 };
|
|
const cellSize = 35;
|
|
const width = cellSize * 7; // Number of columns (days)
|
|
const height = cellSize * 7; // Number of rows (days)
|
|
|
|
// Create a tooltip element
|
|
const tooltip = d3.select("body")
|
|
.append("div")
|
|
.attr("class", "tooltip")
|
|
.style("opacity", 0)
|
|
.style("position", "absolute"); // Set tooltip position to absolute
|
|
|
|
const svg = d3.select(`#${myid}`)
|
|
.append("svg")
|
|
.attr("class", "heatmap")
|
|
.attr("width", width + margin.left + margin.right)
|
|
.attr("height", height + margin.top + margin.bottom)
|
|
.append("g")
|
|
.attr("transform", `translate(${margin.left},${margin.top})`);
|
|
|
|
// Convert data values to numbers
|
|
const numericData = data.map(value => parseFloat(value));
|
|
|
|
// Create a color scale based on numeric data
|
|
const colorScale = d3.scaleLinear()
|
|
.domain([d3.min(numericData), d3.max(numericData)])
|
|
.range(["#0571b0", "#ca0020"]); // Replace with your desired color range
|
|
|
|
const rect = svg.selectAll("rect")
|
|
.data(numericData)
|
|
.enter().append("rect")
|
|
.attr("width", cellSize)
|
|
.attr("height", cellSize)
|
|
.attr("x", (d, i) => ((i + getDayOffset(month, year)) % 7) * cellSize)
|
|
.attr("y", (d, i) => Math.floor((i + getDayOffset(month, year)) / 7) * cellSize)
|
|
.attr("fill", d => colorScale(d)) // Apply the color scale to data values
|
|
.on("mouseover", function (d) {
|
|
// Show the tooltip on hover
|
|
const [x, y] = d3.mouse(this);
|
|
const pageX = x + window.scrollX; // Adjust for horizontal scroll
|
|
const pageY = y + window.scrollY; // Adjust for vertical scroll
|
|
tooltip
|
|
.style("left", (d3.event.pageX + 10) + "px")
|
|
.style("top", (d3.event.pageY - 10) + "px")
|
|
.style("background-color", "#c3c3c3")
|
|
.style("opacity", 1)
|
|
.html("Value: " + d.toFixed(2));
|
|
})
|
|
.on("mouseout", () => {
|
|
tooltip.style("opacity", 0);
|
|
});
|
|
|
|
svg.selectAll(".day-number")
|
|
.data(numericData)
|
|
.enter().append("text")
|
|
.attr("class", "day-number")
|
|
.attr("x", (d, i) => ((i + getDayOffset(month, year)) % 7) * cellSize + cellSize / 2)
|
|
.attr("y", (d, i) => Math.floor((i + getDayOffset(month, year)) / 7) * cellSize + cellSize / 2)
|
|
.attr("text-anchor", "middle")
|
|
.attr("alignment-baseline", "middle")
|
|
.text((d, i) => i + 1);
|
|
|
|
const weekDays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
svg.selectAll(".weekday")
|
|
.data(weekDays)
|
|
.enter().append("text")
|
|
.attr("class", "weekday")
|
|
.attr("x", (d, i) => i * cellSize + cellSize / 2)
|
|
.attr("y", -10)
|
|
.attr("text-anchor", "middle")
|
|
.text((d) => d);
|
|
|
|
const firstDayOfMonth = new Date(year, month - 1, 1);
|
|
const dayOffset = (firstDayOfMonth.getDay() + 6) % 7; // Calculate the day offset for the first day of the month
|
|
const monthName = getMonth(month);
|
|
svg.append("text")
|
|
.attr("class", "year-label")
|
|
.attr("x", width)
|
|
.attr("y", -30)
|
|
.attr("text-anchor", "end")
|
|
.text(monthName + ", " + year);
|
|
|
|
function getDayOffset(month, year) {
|
|
const firstDayOfMonth = new Date(year, month - 1, 1);
|
|
const dayOffset = (firstDayOfMonth.getDay() + 6) % 7; // Calculate the day offset for the first day of the month
|
|
return dayOffset;
|
|
}
|
|
|
|
function getMonth(month) {
|
|
const months = [
|
|
"January", "February", "March", "April", "May", "June",
|
|
"July", "August", "September", "October", "November", "December"
|
|
];
|
|
return months[month - 1];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function do_grid_monthly_one(myid, month, year, data, apiUrl) {
|
|
const margin = { top: 60, right: 20, bottom: 20, left: 20 };
|
|
const cellSize = 35;
|
|
const width = cellSize * 7; // Number of columns (days)
|
|
const height = cellSize * 7; // Number of rows (days)
|
|
|
|
// Create a tooltip element
|
|
const tooltip = d3.select("body")
|
|
.append("div")
|
|
.attr("class", "tooltip")
|
|
.style("opacity", 0)
|
|
.style("position", "absolute"); // Set tooltip position to absolute
|
|
|
|
const svg = d3.select(`#${myid}`)
|
|
.append("svg")
|
|
.attr("class", "heatmap")
|
|
.attr("width", width + margin.left + margin.right)
|
|
.attr("height", height + margin.top + margin.bottom)
|
|
.append("g")
|
|
.attr("transform", `translate(${margin.left},${margin.top})`);
|
|
|
|
// Convert data values to numbers
|
|
const numericData = data.map(value => parseFloat(value));
|
|
|
|
// Create a color scale based on numeric data
|
|
const colorScale = d3.scaleLinear()
|
|
.domain([d3.min(numericData), d3.max(numericData)])
|
|
.range(["#0571b0", "#ca0020"]); // Replace with your desired color range
|
|
|
|
const rect = svg.selectAll("rect")
|
|
.data(numericData)
|
|
.enter().append("rect")
|
|
.attr("width", cellSize)
|
|
.attr("height", cellSize)
|
|
.attr("x", (d, i) => ((i + getDayOffset(month, year)) % 7) * cellSize)
|
|
.attr("y", (d, i) => Math.floor((i + getDayOffset(month, year)) / 7) * cellSize)
|
|
.attr("fill", d => colorScale(d)) // Apply the color scale to data values
|
|
.on("mouseover", function (d) {
|
|
// Show the tooltip on hover
|
|
const [x, y] = d3.mouse(this);
|
|
const pageX = x + window.scrollX; // Adjust for horizontal scroll
|
|
const pageY = y + window.scrollY; // Adjust for vertical scroll
|
|
tooltip
|
|
.style("left", (d3.event.pageX + 10) + "px")
|
|
.style("top", (d3.event.pageY - 10) + "px")
|
|
.style("background-color", "#c3c3c3")
|
|
.style("opacity", 1)
|
|
.html("Value: " + d.toFixed(2));
|
|
})
|
|
.on("mouseout", () => {
|
|
tooltip.style("opacity", 0);
|
|
});
|
|
|
|
svg.selectAll(".day-number")
|
|
.data(numericData)
|
|
.enter().append("text")
|
|
.attr("class", "day-number")
|
|
.attr("x", (d, i) => ((i + getDayOffset(month, year)) % 7) * cellSize + cellSize / 2)
|
|
.attr("y", (d, i) => Math.floor((i + getDayOffset(month, year)) / 7) * cellSize + cellSize / 2)
|
|
.attr("text-anchor", "middle")
|
|
.attr("alignment-baseline", "middle")
|
|
.text((d, i) => i + 1);
|
|
|
|
const weekDays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
svg.selectAll(".weekday")
|
|
.data(weekDays)
|
|
.enter().append("text")
|
|
.attr("class", "weekday")
|
|
.attr("x", (d, i) => i * cellSize + cellSize / 2)
|
|
.attr("y", -10)
|
|
.attr("text-anchor", "middle")
|
|
.text((d) => d);
|
|
|
|
const firstDayOfMonth = new Date(year, month - 1, 1);
|
|
const dayOffset = (firstDayOfMonth.getDay() + 6) % 7; // Calculate the day offset for the first day of the month
|
|
const monthName = getMonth(month);
|
|
svg.append("text")
|
|
.attr("class", "year-label")
|
|
.attr("x", width)
|
|
.attr("y", -30)
|
|
.attr("text-anchor", "end")
|
|
.text(monthName + ", " + year);
|
|
|
|
function getDayOffset(month, year) {
|
|
const firstDayOfMonth = new Date(year, month - 1, 1);
|
|
const dayOffset = (firstDayOfMonth.getDay() + 6) % 7; // Calculate the day offset for the first day of the month
|
|
return dayOffset;
|
|
}
|
|
|
|
function getMonth(month) {
|
|
const months = [
|
|
"January", "February", "March", "April", "May", "June",
|
|
"July", "August", "September", "October", "November", "December"
|
|
];
|
|
return months[month - 1];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------
|
|
// GRID YEARLY d3 diagram
|
|
function do_grid_yearly(myid, year, myData) {
|
|
// Remove the existing SVG if it exists
|
|
d3.select(`#${myid} svg`).remove();
|
|
|
|
// For demonstration purposes, let's create a simple SVG rectangle
|
|
const svg = d3.select(`#${myid}`).append("svg")
|
|
.attr("width", 800)
|
|
.attr("height", 650); // Increased height for a taller diagram
|
|
|
|
// Append the 'months' group inside the SVG
|
|
const monthsGroup = svg.append("g")
|
|
.attr("id", "months");
|
|
|
|
const months = [
|
|
{ name: 'January', days: 31 },
|
|
{ name: 'February', days: 28 },
|
|
{ name: 'March', days: 31 },
|
|
{ name: 'April', days: 30 },
|
|
{ name: 'May', days: 31 },
|
|
{ name: 'June', days: 30 },
|
|
{ name: 'July', days: 31 },
|
|
{ name: 'August', days: 31 },
|
|
{ name: 'September', days: 30 },
|
|
{ name: 'October', days: 31 },
|
|
{ name: 'November', days: 30 },
|
|
{ name: 'December', days: 31 },
|
|
];
|
|
|
|
// Number of columns and rows
|
|
const columns = 4;
|
|
const rows = 5; // Increased rows for more vertical space
|
|
|
|
// Calculate cell width and height
|
|
const cellWidth = 660 / columns;
|
|
const cellHeight = (800 / rows); // Increased cell height to 40px per month
|
|
|
|
// Border width
|
|
const borderWidth = 1;
|
|
|
|
// Calculate min and max values for the color scale
|
|
const minValue = Math.min(...myData);
|
|
const maxValue = Math.max(...myData);
|
|
const valueRange = maxValue - minValue;
|
|
|
|
// Create a color scale based on the min, max, and range of values
|
|
const colorScale = d3.scaleLinear()
|
|
.domain([minValue, maxValue])
|
|
.range([0, 1]);
|
|
|
|
// Create a group for month labels
|
|
const monthLabelsGroup = svg.append("g")
|
|
.attr("id", "month-labels");
|
|
|
|
// Function to handle mouseover event and display tooltip
|
|
function handleMouseover(day, value) {
|
|
// Show the tooltip on hover
|
|
tooltip
|
|
.style("left", (d3.event.pageX + 10) + "px")
|
|
.style("top", (d3.event.pageY - 10) + "px")
|
|
.style("background-color", "#c3c3c3")
|
|
.style("opacity", 1)
|
|
.html(`Value: ${value}`);
|
|
}
|
|
|
|
// Create a tooltip element
|
|
const tooltip = d3.select("body")
|
|
.append("div")
|
|
.attr("class", "tooltip")
|
|
.style("opacity", 0);
|
|
|
|
// Loop through each month and create the calendar heatmap
|
|
for (let i = 0; i < months.length; i++) {
|
|
const month = months[i];
|
|
// Calculate row and column positions
|
|
const row = Math.floor(i / columns);
|
|
const col = i % columns;
|
|
|
|
// Starting positions for months
|
|
const x = col * cellWidth + borderWidth;
|
|
const y = row * cellHeight + borderWidth;
|
|
|
|
// Month Label
|
|
const monthLabel = monthLabelsGroup.append("text")
|
|
.attr("x", x + cellWidth / 2)
|
|
.attr("y", y + 20) // Increased vertical position
|
|
.attr("font-size", "18")
|
|
.attr("text-anchor", "middle")
|
|
.text(`${month.name} ${year}`);
|
|
|
|
// Create a border around the month
|
|
const border = svg.append("rect")
|
|
.attr("x", x)
|
|
.attr("y", y + 30) // Increased vertical position
|
|
.attr("width", cellWidth - 2 * borderWidth)
|
|
.attr("height", cellHeight - 2 * borderWidth - 30) // Adjusted height to avoid overlap
|
|
.attr("fill", "none")
|
|
.attr("stroke", "#ffffff") // Border
|
|
.attr("stroke-width", borderWidth);
|
|
|
|
// Calendar Days
|
|
for (let day = 1; day <= month.days; day++) {
|
|
// Get the value for the current day
|
|
const value = myData[month.days * i + day - 1]; // Calculate index based on month and day
|
|
|
|
// Calculate the color based on the value
|
|
const color = d3.interpolateViridis(colorScale(value));
|
|
|
|
// Calculate the position of the current day
|
|
const dayRow = Math.floor((day - 1) / 7);
|
|
const dayCol = (day - 1) % 7;
|
|
|
|
|
|
|
|
function getDayOffset(month, year) {
|
|
const firstDayOfMonth = new Date(year, month - 1, 1);
|
|
const dayOffset = (firstDayOfMonth.getDay() + 6) % 7; // Calculate the day offset for the first day of the month
|
|
return dayOffset;
|
|
}
|
|
|
|
}
|
|
// Weekday labels
|
|
const weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
|
|
|
// Loop through each month and create the calendar heatmap
|
|
for (let i = 0; i < months.length; i++) {
|
|
const month = months[i];
|
|
// Calculate row and column positions
|
|
const row = Math.floor(i / columns);
|
|
const col = i % columns;
|
|
|
|
// Starting positions for months
|
|
const x = col * cellWidth + borderWidth;
|
|
const y = row * cellHeight + borderWidth;
|
|
|
|
|
|
// Weekday labels
|
|
for (let w = 0; w < weekdays.length; w++) {
|
|
const weekdayLabel = svg.append("text")
|
|
.attr("x", x + (w * (cellWidth / 7)) + cellWidth / 14)
|
|
.attr("y", y + 55) // Increased vertical position
|
|
.attr("font-size", "12")
|
|
.attr("text-anchor", "middle")
|
|
.text(weekdays[w]);
|
|
}
|
|
|
|
|
|
// Calendar Days
|
|
for (let day = 1; day <= month.days; day++) {
|
|
// Get the value for the current day
|
|
const value = myData[month.days * i + day - 1]; // Calculate index based on month and day
|
|
|
|
// Calculate the color based on the value
|
|
const color = d3.interpolateViridis(colorScale(value));
|
|
|
|
// Calculate the position of the current day
|
|
const dayRow = Math.floor((day - 1) / 7);
|
|
const dayCol = (day - 1) % 7;
|
|
|
|
// Create a rectangle for each day
|
|
svg.append("rect")
|
|
.attr("x", x + (dayCol * (cellWidth / 7)))
|
|
.attr("y", y + 60 + (dayRow * (cellHeight / 6))) // Increased vertical position
|
|
.attr("width", cellWidth / 7)
|
|
.attr("height", cellHeight / 6)
|
|
.attr("fill", color)
|
|
.attr("stroke", "#ffffff") // white border
|
|
.attr("class", "day")
|
|
.on("mouseover", () => {
|
|
const dayValue = myData[month.days * i + day - 1]; // Calculate index based on month and day
|
|
// Call the handleMouseover function with the correct value
|
|
handleMouseover(day, dayValue);
|
|
})
|
|
.on("mouseout", () => {
|
|
tooltip.style("opacity", 0);
|
|
})
|
|
.append("text") // Add text to display the day number
|
|
.attr("x", x + (dayCol * (cellWidth / 7)) + cellWidth / 14)
|
|
.attr("y", y + 85 + (dayRow * (cellHeight / 6))) // Increased vertical position
|
|
.attr("font-size", "12")
|
|
.attr("text-anchor", "middle")
|
|
.text(day);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
function do_grid_yearlyXX(myid, year, myData) {
|
|
// Remove the existing SVG if it exists
|
|
d3.select(`#${myid} svg`).remove();
|
|
|
|
// For demonstration purposes, let's create a simple SVG rectangle
|
|
const svg = d3.select(`#${myid}`).append("svg")
|
|
.attr("width", 800)
|
|
.attr("height", 650); // Increased height for a taller diagram
|
|
|
|
// Append the 'months' group inside the SVG
|
|
const monthsGroup = svg.append("g")
|
|
.attr("id", "months");
|
|
|
|
const months = [
|
|
{ name: 'January', days: 31 },
|
|
{ name: 'February', days: 28 },
|
|
{ name: 'March', days: 31 },
|
|
{ name: 'April', days: 30 },
|
|
{ name: 'May', days: 31 },
|
|
{ name: 'June', days: 30 },
|
|
{ name: 'July', days: 31 },
|
|
{ name: 'August', days: 31 },
|
|
{ name: 'September', days: 30 },
|
|
{ name: 'October', days: 31 },
|
|
{ name: 'November', days: 30 },
|
|
{ name: 'December', days: 31 },
|
|
];
|
|
|
|
// Number of columns and rows
|
|
const columns = 4;
|
|
const rows = 5; // Increased rows for more vertical space
|
|
|
|
// Calculate cell width and height
|
|
const cellWidth = 660 / columns;
|
|
const cellHeight = (800 / rows); // Increased cell height to 40px per month
|
|
|
|
// Border width
|
|
const borderWidth = 1;
|
|
|
|
// Calculate min and max values for the color scale
|
|
const minValue = Math.min(...myData);
|
|
const maxValue = Math.max(...myData);
|
|
const valueRange = maxValue - minValue;
|
|
|
|
// Create a color scale based on the min, max, and range of values
|
|
const colorScale = d3.scaleLinear()
|
|
.domain([minValue, maxValue])
|
|
.range([0, 1]);
|
|
|
|
// Create a group for month labels
|
|
const monthLabelsGroup = svg.append("g")
|
|
.attr("id", "month-labels");
|
|
|
|
// Function to handle mouseover event and display tooltip
|
|
function handleMouseover(day, value) {
|
|
// Show the tooltip on hover
|
|
tooltip
|
|
.style("left", (d3.event.pageX + 10) + "px")
|
|
.style("top", (d3.event.pageY - 10) + "px")
|
|
.style("background-color", "#c3c3c3")
|
|
.style("opacity", 1)
|
|
.html(`Value: ${value}`);
|
|
}
|
|
|
|
// Create a tooltip element
|
|
const tooltip = d3.select("body")
|
|
.append("div")
|
|
.attr("class", "tooltip")
|
|
.style("opacity", 0);
|
|
|
|
// Loop through each month and create the calendar heatmap
|
|
for (let i = 0; i < months.length; i++) {
|
|
const month = months[i];
|
|
// Calculate row and column positions
|
|
const row = Math.floor(i / columns);
|
|
const col = i % columns;
|
|
|
|
// Starting positions for months
|
|
const x = col * cellWidth + borderWidth;
|
|
const y = row * cellHeight + borderWidth;
|
|
|
|
// Month Label
|
|
const monthLabel = monthLabelsGroup.append("text")
|
|
.attr("x", x + cellWidth / 2)
|
|
.attr("y", y + 20) // Increased vertical position
|
|
.attr("font-size", "18")
|
|
.attr("text-anchor", "middle")
|
|
.text(`${month.name} ${year}`);
|
|
|
|
// Create a border around the month
|
|
const border = svg.append("rect")
|
|
.attr("x", x)
|
|
.attr("y", y + 30) // Increased vertical position
|
|
.attr("width", cellWidth - 2 * borderWidth)
|
|
.attr("height", cellHeight - 2 * borderWidth - 30) // Adjusted height to avoid overlap
|
|
.attr("fill", "none")
|
|
.attr("stroke", "#ffffff") // Border
|
|
.attr("stroke-width", borderWidth);
|
|
|
|
// Calendar Days
|
|
for (let day = 1; day <= month.days; day++) {
|
|
// Get the value for the current day
|
|
const value = myData[month.days * i + day - 1]; // Calculate index based on month and day
|
|
|
|
// Calculate the color based on the value
|
|
const color = d3.interpolateViridis(colorScale(value));
|
|
|
|
// Calculate the position of the current day
|
|
const dayRow = Math.floor((day - 1) / 7);
|
|
const dayCol = (day - 1) % 7;
|
|
|
|
// Create a rectangle for each day
|
|
svg.append("rect")
|
|
.attr("x", x + (dayCol * (cellWidth / 7)))
|
|
.attr("y", y + 60 + (dayRow * (cellHeight / 6))) // Increased vertical position
|
|
.attr("width", cellWidth / 7)
|
|
.attr("height", cellHeight / 6)
|
|
.attr("fill", color)
|
|
.attr("stroke", "#ffffff") // white border
|
|
.attr("class", "day")
|
|
.on("mouseover", () => {
|
|
const dayValue = myData[month.days * i + day - 1]; // Calculate index based on month and day
|
|
// Call the handleMouseover function with the correct value
|
|
handleMouseover(day, dayValue);
|
|
})
|
|
.on("mouseout", () => {
|
|
tooltip.style("opacity", 0);
|
|
});
|
|
}
|
|
// Weekday labels
|
|
const weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
|
|
|
// Loop through each month and create the calendar heatmap
|
|
for (let i = 0; i < months.length; i++) {
|
|
const month = months[i];
|
|
// Calculate row and column positions
|
|
const row = Math.floor(i / columns);
|
|
const col = i % columns;
|
|
|
|
// Starting positions for months
|
|
const x = col * cellWidth + borderWidth;
|
|
const y = row * cellHeight + borderWidth;
|
|
|
|
// Month Label
|
|
const monthLabel = monthLabelsGroup.append("text")
|
|
.attr("x", x + cellWidth / 2)
|
|
.attr("y", y + 20) // Increased vertical position
|
|
.attr("font-size", "18")
|
|
.attr("text-anchor", "middle")
|
|
.text(`${month.name} ${year}`);
|
|
|
|
// Weekday labels
|
|
for (let w = 0; w < weekdays.length; w++) {
|
|
const weekdayLabel = svg.append("text")
|
|
.attr("x", x + (w * (cellWidth / 7)) + cellWidth / 14)
|
|
.attr("y", y + 55) // Increased vertical position
|
|
.attr("font-size", "12")
|
|
.attr("text-anchor", "middle")
|
|
.text(weekdays[w]);
|
|
}
|
|
|
|
// Create a border around the month
|
|
const border = svg.append("rect")
|
|
.attr("x", x)
|
|
.attr("y", y + 30) // Increased vertical position
|
|
.attr("width", cellWidth - 2 * borderWidth)
|
|
.attr("height", cellHeight - 2 * borderWidth - 30) // Adjusted height to avoid overlap
|
|
.attr("fill", "none")
|
|
.attr("stroke", "#ffffff") // Border
|
|
.attr("stroke-width", borderWidth);
|
|
|
|
// Calendar Days
|
|
for (let day = 1; day <= month.days; day++) {
|
|
// Get the value for the current day
|
|
const value = myData[month.days * i + day - 1]; // Calculate index based on month and day
|
|
|
|
// Calculate the color based on the value
|
|
const color = d3.interpolateViridis(colorScale(value));
|
|
|
|
// Calculate the position of the current day
|
|
const dayRow = Math.floor((day - 1) / 7);
|
|
const dayCol = (day - 1) % 7;
|
|
|
|
// Create a rectangle for each day
|
|
svg.append("rect")
|
|
.attr("x", x + (dayCol * (cellWidth / 7)))
|
|
.attr("y", y + 60 + (dayRow * (cellHeight / 6))) // Increased vertical position
|
|
.attr("width", cellWidth / 7)
|
|
.attr("height", cellHeight / 6)
|
|
.attr("fill", color)
|
|
.attr("stroke", "#ffffff") // white border
|
|
.attr("class", "day")
|
|
.on("mouseover", () => {
|
|
const dayValue = myData[month.days * i + day - 1]; // Calculate index based on month and day
|
|
// Call the handleMouseover function with the correct value
|
|
handleMouseover(day, dayValue);
|
|
})
|
|
.on("mouseout", () => {
|
|
tooltip.style("opacity", 0);
|
|
})
|
|
.append("text") // Add text to display the day number
|
|
.attr("x", x + (dayCol * (cellWidth / 7)) + cellWidth / 14)
|
|
.attr("y", y + 85 + (dayRow * (cellHeight / 6))) // Increased vertical position
|
|
.attr("font-size", "12")
|
|
.attr("text-anchor", "middle")
|
|
.text(day);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
|
|
|
|
|
|
|
|
|
|
//--------------------------------
|
|
// HORIZON d3 diagram
|
|
function do_horizon_row(data, myid, title, myDates) {
|
|
var horizonChart = d3.horizonChart()
|
|
.height(50)
|
|
.title(title)
|
|
.colors(['#313695', '#4575b4', '#74add1', '#abd9e9', '#fee090', '#fdae61', '#f46d43', '#d73027']);
|
|
|
|
var horizons1 = d3.select(myid).selectAll('.horizon1')
|
|
.data([data])
|
|
.enter()
|
|
.append('div')
|
|
.attr('class', 'horizon')
|
|
.attr('title', function (d, i) {
|
|
return i;
|
|
})
|
|
.each(horizonChart)
|
|
.each(function (d, i) {
|
|
var canvas = d3.select(this).select('canvas');
|
|
|
|
canvas.on('mouseover', function (dd, i) {
|
|
// Get the mouse position relative to the canvas
|
|
var [x, y] = d3.mouse(this);
|
|
|
|
// Calculate the y-value based on the mouse position
|
|
var chartHeight = horizonChart.height();
|
|
var yValue = (y / chartHeight) * (d[0][0].max - d[0][0].min) + d[0][0].min;
|
|
|
|
// Retrieve the corresponding date
|
|
var dateIndex = Math.floor(x / (canvas.node().width / myDates.length));
|
|
var date = myDates[dateIndex];
|
|
|
|
// Create a tooltip element
|
|
var tooltip = d3.select('body').append('div')
|
|
.attr('class', 'tooltip')
|
|
.style('background-color', 'rgba(255, 255, 255, 0.8)')
|
|
.style('padding', '5px')
|
|
.style('border', '1px solid #ddd')
|
|
.style('border-radius', '3px')
|
|
.style('pointer-events', 'none')
|
|
.style('font-size', '12px');
|
|
|
|
// Position the tooltip next to the mouse
|
|
tooltip
|
|
.style('right', (x + 2) + 'px')
|
|
.style('top', (y + 10) + 'px')
|
|
.style('position', 'relative');
|
|
|
|
// Display y value and date
|
|
tooltip.html(`Date: ${date}<br>Value: ${yValue}`);
|
|
|
|
// Remove the tooltip when mouseout
|
|
canvas.on('mouseout', function () {
|
|
tooltip.remove();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
|
|
|
|
// FUNCTIONS /////////////////////////////////////////////////
|
|
// Function interactions
|
|
|
|
|
|
|
|
///////////////////////////////////////////
|
|
// Div diagrams buttons: remove, popup:
|
|
|
|
// Function to remove the div
|
|
function removeDiv(button) {
|
|
const div = button.parentElement;
|
|
div.remove();
|
|
}
|
|
|
|
// Function to open a new window with the div content
|
|
function openPopup(button) {
|
|
const divContent = button.parentElement.innerHTML;
|
|
const newWindow = window.open('', '', 'width=600,height=400');
|
|
newWindow.document.write('<html><head><title>Popup Window</title></head><body>');
|
|
newWindow.document.write('<div>' + divContent + '</div>');
|
|
newWindow.document.write('</body></html>');
|
|
newWindow.document.close();
|
|
}
|
|
|
|
function openPopupXX(button) {
|
|
const divContent = button.parentElement.innerHTML;
|
|
|
|
// Open a new window
|
|
const newWindow = window.open('', '', 'width=600,height=400');
|
|
|
|
// Write the basic HTML structure to the new window
|
|
newWindow.document.write('<html><head><title>Popup Window</title></head><body>');
|
|
|
|
// Copy the content of the main page's head to the new window's head
|
|
const mainPageHead = document.head;
|
|
const newWindowHead = newWindow.document.head;
|
|
for (let i = 0; i < mainPageHead.children.length; i++) {
|
|
const element = mainPageHead.children[i];
|
|
if (element.tagName === 'LINK' || element.tagName === 'STYLE' || element.tagName === 'SCRIPT') {
|
|
// Copy link, style, and script elements
|
|
newWindowHead.appendChild(element.cloneNode(true));
|
|
}
|
|
}
|
|
|
|
// Write the content of the DIV to the new window's body
|
|
newWindow.document.write('<div>' + divContent + '</div>');
|
|
|
|
// Close the HTML structure
|
|
newWindow.document.write('</body></html>');
|
|
|
|
// Close the new window document
|
|
newWindow.document.close();
|
|
}
|
|
|
|
// Open popup only for Hotizon in <canvas>
|
|
function openPopupCanvas(button) {
|
|
|
|
function exportCanvasToNewWindow(dataURLs, textContents, titles, initialTitle) {
|
|
// Open a new window
|
|
const newWindow = window.open('', '', 'width=800,height=400');
|
|
|
|
// Write the basic HTML structure to the new window
|
|
newWindow.document.write('<html><head><title>Canvas Export</title></head><body>');
|
|
|
|
// Create an <h2> element for the initialTitle and prepend it to the container
|
|
const initialH2 = newWindow.document.createElement('h2');
|
|
initialH2.textContent = initialTitle;
|
|
newWindow.document.body.appendChild(initialH2);
|
|
|
|
// Create a container div for canvas, text, and titles
|
|
const containerDiv = newWindow.document.createElement('div');
|
|
containerDiv.style.display = 'flex';
|
|
containerDiv.style.flexWrap = 'wrap'; // Allow elements to wrap to the next line
|
|
|
|
// Loop through each canvas, text, and title
|
|
for (let i = 0; i < dataURLs.length; i++) {
|
|
// Create a div for each canvas, text, and title
|
|
const div = newWindow.document.createElement('div');
|
|
div.style.marginRight = '20px'; // Add spacing between elements
|
|
|
|
// Create an image element with the captured canvas content
|
|
const canvasImage = new Image();
|
|
canvasImage.src = dataURLs[i];
|
|
div.appendChild(canvasImage);
|
|
|
|
// Create a <p> element for the text content
|
|
const textElement = newWindow.document.createElement('p');
|
|
textElement.textContent = textContents[i];
|
|
div.appendChild(textElement);
|
|
|
|
// Create a <span> element for the title content
|
|
const titleElement = newWindow.document.createElement('span');
|
|
titleElement.className = 'title';
|
|
titleElement.textContent = titles[i];
|
|
div.appendChild(titleElement);
|
|
|
|
// Append the div to the container
|
|
containerDiv.appendChild(div);
|
|
}
|
|
|
|
// Append the container div to the new window
|
|
newWindow.document.body.appendChild(containerDiv);
|
|
|
|
// Close the HTML structure
|
|
newWindow.document.write('</body></html>');
|
|
newWindow.document.close();
|
|
}
|
|
|
|
// Get all the canvas, text, and title elements inside the div with id "col-right"
|
|
const colRightDiv = document.getElementById('col-right');
|
|
const canvasElements = colRightDiv.querySelectorAll('canvas');
|
|
const textElements = colRightDiv.querySelectorAll('p');
|
|
const titleElements = colRightDiv.querySelectorAll('.title');
|
|
|
|
// Extract data URLs from canvas elements
|
|
const dataURLs = Array.from(canvasElements).map((canvas) => canvas.toDataURL());
|
|
|
|
// Extract text content from <p> elements
|
|
const textContents = Array.from(textElements).map((p) => p.textContent);
|
|
|
|
// Extract title content from <span class="title"> elements
|
|
const titles = Array.from(titleElements).map((span) => span.textContent);
|
|
|
|
// Define the initialTitle
|
|
const initialTitle = "Trä hus data flexiboard , powered by infravis.se"; // Replace with your desired title
|
|
|
|
// Call the function to export all canvas, text, and title content to a new window
|
|
exportCanvasToNewWindow(dataURLs, textContents, titles, initialTitle);
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////
|
|
// Template 3 columns 100%
|
|
const colLeft = document.getElementById("col-left");
|
|
const colCenter = document.getElementById("col-center");
|
|
const colRight = document.getElementById("col-right");
|
|
const button1 = document.getElementById("button1");
|
|
const button2 = document.getElementById("button2");
|
|
|
|
button1.addEventListener("click", () => {
|
|
// Set the width of columns to 100%
|
|
colLeft.style.width = "100%";
|
|
colCenter.style.width = "100%";
|
|
colRight.style.width = "100%";
|
|
});
|
|
|
|
button2.addEventListener("click", () => {
|
|
// Reset the width of columns to 30%
|
|
colLeft.style.width = "30%";
|
|
colCenter.style.width = "30%";
|
|
colRight.style.width = "30%";
|
|
});
|
|
|
|
////////////////////////////
|
|
////// FORMS LISTENER:
|
|
function getFormData(formId) {
|
|
const form = document.getElementById(formId);
|
|
const formData = {};
|
|
|
|
for (const input of form.querySelectorAll('input')) {
|
|
formData[input.name] = input.value;
|
|
}
|
|
|
|
return formData;
|
|
}
|
|
|
|
// Attach click event listeners to submit buttons
|
|
document.querySelectorAll('button[type="submit"]').forEach(button => {
|
|
button.addEventListener('click', function(event) {
|
|
event.preventDefault(); // Prevent form submission
|
|
// Get the form ID associated with the clicked button
|
|
const formId = event.target.closest('form').id;
|
|
// Get the form data based on the form ID
|
|
const formData = getFormData(formId);
|
|
// Is it a diagram or a text?
|
|
if (formId.substring(1)=="Text") {
|
|
var fromEditor = document.getElementById(formId[0]+"editor").innerHTML;
|
|
// Parsing the text:
|
|
var parser = new DOMParser();
|
|
var doc = parser.parseFromString(fromEditor, 'text/html');
|
|
var div = doc.querySelector('div[data-lt-tmp-id^="lt-"]');
|
|
var textData = div ? div.innerHTML : 'No div found';
|
|
writeInTXT(formId, textData);
|
|
} if (formId == "saveBoardForm") {
|
|
var boardName = document.getElementById("boardName").value;
|
|
var boardDesc = document.getElementById("boardDesc").value
|
|
getCurrentBoardData(boardName, boardDesc);
|
|
} else {
|
|
writeIn(formId, formData);
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
// Get Week number from a Data
|
|
function getWeekNumberFromDate(dateString) {
|
|
// Parse the date string into day, month, and year
|
|
var parts = dateString.split('/');
|
|
var day = parseInt(parts[0], 10);
|
|
var month = parseInt(parts[1], 10);
|
|
var year = parseInt(parts[2], 10);
|
|
|
|
// Create a new Date object
|
|
var dateObject = new Date(year, month - 1, day);
|
|
|
|
// Calculate the week number
|
|
function getWeekNumber(date) {
|
|
var d = new Date(date);
|
|
d.setHours(0, 0, 0, 0);
|
|
d.setDate(d.getDate() + 3 - ((d.getDay() + 6) % 7));
|
|
var week1 = new Date(d.getFullYear(), 0, 4);
|
|
return 1 + Math.round(((d - week1) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7);
|
|
}
|
|
|
|
var weekNumber = getWeekNumber(dateObject);
|
|
|
|
return weekNumber;
|
|
}
|
|
|
|
//////////////////////////////
|
|
// FORMS: rich text editor
|
|
// Initialize Quill editors
|
|
const editors = document.querySelectorAll('.mytextarea');
|
|
editors.forEach((editor) => {
|
|
new Quill(editor, {
|
|
theme: 'snow'
|
|
});
|
|
});
|
|
function handleSubmit(event) {
|
|
event.preventDefault();
|
|
|
|
// Find the Quill editor within the form
|
|
const editor = event.target.querySelector('.mytextarea .ql-editor');
|
|
|
|
// Access the editor's HTML content
|
|
const editorContent = editor.innerHTML;
|
|
|
|
// Do something with the editorContent (e.g., send it to the server)
|
|
console.log(editorContent);
|
|
}
|
|
|
|
///////////////////
|
|
// FORMS Check validator required fields:
|
|
//
|
|
// ...
|
|
|
|
|
|
|
|
//////////////////////ç//
|
|
// Columns mouse interaction
|
|
const columns = document.querySelectorAll('.resizable-column');
|
|
let isResizing = false;
|
|
let currentColumn = null;
|
|
let initialX = null;
|
|
let initialWidth = null;
|
|
|
|
columns.forEach(column => {
|
|
column.addEventListener('mousedown', (e) => {
|
|
isResizing = true;
|
|
currentColumn = column;
|
|
initialX = e.clientX;
|
|
initialWidth = parseFloat(getComputedStyle(currentColumn).width);
|
|
});
|
|
});
|
|
|
|
document.addEventListener('mousemove', (e) => {
|
|
if (!isResizing) return;
|
|
const deltaX = e.clientX - initialX;
|
|
const newWidth = initialWidth + deltaX;
|
|
currentColumn.style.width = `${newWidth}px`;
|
|
});
|
|
|
|
document.addEventListener('mouseup', () => {
|
|
isResizing = false;
|
|
currentColumn = null;
|
|
initialX = null;
|
|
initialWidth = null;
|
|
});
|
|
|
|
|
|
//////////////////////////////////////////////////////
|
|
// Simple funcion to handle DiagramType <-> columns
|
|
function getName(name) {
|
|
const nameMap = {
|
|
"col-left": "P",
|
|
"col-center": "G",
|
|
"col-right": "H",
|
|
};
|
|
// Check if the input name exists in the map
|
|
if (nameMap.hasOwnProperty(name)) {
|
|
return nameMap[name];
|
|
} else {
|
|
// Reverse lookup: Find the key for the given value
|
|
for (const key in nameMap) {
|
|
if (nameMap[key] === name) {
|
|
return key;
|
|
}
|
|
}
|
|
// Return the input name if no match is found
|
|
return name;
|
|
}
|
|
}
|
|
|
|
////////////////
|
|
// Shortening console.log()
|
|
function cl(print) {return console.log(print)};
|
|
|
|
// Include on the fly files
|
|
function require(script) {
|
|
$.ajax({
|
|
url: script,
|
|
dataType: "script",
|
|
async: false, // <-- This is the key
|
|
success: function () {
|
|
// all good...
|
|
},
|
|
error: function () {
|
|
throw new Error("Could not load script " + script);
|
|
}
|
|
});
|
|
}
|
|
|
|
///////////////////////////
|
|
// DRAGGAVLE DIVs Vertically
|
|
$(document).ready(function() {
|
|
// Initialize sortable for col-left
|
|
$("#col-left").sortable({
|
|
items: ".sortable-item",
|
|
cursor: "move",
|
|
axis: "y", // Sort vertically
|
|
containment: "#col-left", // Contain within the parent div
|
|
});
|
|
|
|
// Initialize sortable for col-center
|
|
$("#col-center").sortable({
|
|
items: ".sortable-item",
|
|
cursor: "move",
|
|
axis: "y", // Sort vertically
|
|
containment: "#col-center", // Contain within the parent div
|
|
});
|
|
|
|
// Initialize sortable for col-right
|
|
$("#col-right").sortable({
|
|
items: ".sortable-item",
|
|
cursor: "move",
|
|
axis: "y", // Sort vertically
|
|
containment: "#col-right", // Contain within the parent div
|
|
});
|
|
});
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
//// DATE/TIME DIALOGS FOR DIAGRAM GENERATOR
|
|
|
|
// Range dates formatted:
|
|
var first_date = first_year_month.split("-")
|
|
var myStartDate_MM_YYYY = first_date[1]+"/"+first_date[0];
|
|
var last_date = last_year_month.split("-")
|
|
var myEndDate_MM_YYYY = last_date[1]+"/"+last_date[0];
|
|
|
|
// Used in Parallel-yearly, Parallel-monthly
|
|
$('#SelectYearMonth, #SelectYearMonth1, #grid-SelectYearMonth, #horizon-SelectYearMonth').datepicker({
|
|
format: "mm/yyyy",
|
|
weekStart: 1,
|
|
startDate: myStartDate_MM_YYYY,
|
|
endDate: myEndDate_MM_YYYY,
|
|
startView: 1,
|
|
minViewMode: 1,
|
|
maxViewMode: 2,
|
|
clearBtn: true,
|
|
orientation: "bottom auto",
|
|
calendarWeeks: true
|
|
});
|
|
|
|
// used in Parallel-monthly
|
|
$('#SelectYearMonthDay, #SelectYearMonthDay1').datepicker({
|
|
format: "dd/mm/yyyy",
|
|
weekStart: 1,
|
|
startDate: "01/"+myStartDate_MM_YYYY,
|
|
endDate: "31/"+myEndDate_MM_YYYY,
|
|
maxViewMode: 2,
|
|
clearBtn: true,
|
|
orientation: "bottom auto",
|
|
calendarWeeks: true
|
|
});
|
|
|
|
// used in Parallel-weekly, Grid-wekkly, and HorizonWeekly
|
|
$('#SelectYearMonthWeek, #horizonSelectYearMonthWeek, #parallelSelectYearMonthWeek').datepicker({
|
|
format: "dd/mm/yyyy",
|
|
weekStart: 1,
|
|
startDate: "01/"+myStartDate_MM_YYYY,
|
|
endDate: "31/"+myEndDate_MM_YYYY,
|
|
maxViewMode: 2,
|
|
clearBtn: true,
|
|
orientation: "bottom auto",
|
|
calendarWeeks: true,
|
|
daysOfWeekDisabled: "0,2,3,4,5,6"
|
|
});
|
|
|
|
|
|
// time picker:
|
|
var firstOpen = true;
|
|
var time;
|
|
|
|
$('#timePicker, #grid-timePicker').datetimepicker({
|
|
useCurrent: false,
|
|
format: "hh:mm A"
|
|
}).on('dp.show', function() {
|
|
if(firstOpen) {
|
|
time = moment().startOf('day');
|
|
firstOpen = false;
|
|
} else {
|
|
time = "12:00 AM"
|
|
}
|
|
$(this).data('DateTimePicker').date(time);
|
|
});
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// FORMS management:
|
|
// > makeApiRequiest
|
|
// > submit nuttons listener
|
|
|
|
function getValueByKeyFrrmUrl(key, apiurl) {
|
|
const keyValuePairs = apiurl.split("&");
|
|
for (let i = 0; i < keyValuePairs.length; i++) {
|
|
const [currentKey, value] = keyValuePairs[i].split("=");
|
|
if (currentKey === key) {
|
|
return value;
|
|
}
|
|
}
|
|
return null; // Return null if the key is not found
|
|
}
|
|
|
|
function getFormattedDateFromWeek(week, year) {
|
|
const date = new Date(year, 0, 1 + (week - 1) * 7); // January 1st + (week - 1) * 7 days
|
|
const day = date.getDate();
|
|
const month = date.getMonth() + 1; // Months are zero-based, so we add 1
|
|
const formattedDate = `${day.toString().padStart(2, '0')}-${month.toString().padStart(2, '0')}-${year}`;
|
|
|
|
return formattedDate;
|
|
}
|
|
|
|
// Checking if any variable for the API query is null and NaN
|
|
function checkEmptyVariable(str) {
|
|
const queryString = str.split("?")[1];
|
|
if (!queryString) {
|
|
console.log("No variables found after the '?' symbol");
|
|
return false;
|
|
}
|
|
|
|
const variables = queryString.split("&");
|
|
|
|
for (let variable of variables) {
|
|
const [key, value] = variable.split("=");
|
|
|
|
if (key !== "hus" && (!value || value.trim() === "undefined" || value.toString().includes("undefined"))) {
|
|
var missedVar = `Variable "${key}" is empty, undefined, or NaN`;
|
|
console.log(missedVar);
|
|
console.log(str);
|
|
return missedVar;
|
|
}
|
|
}
|
|
|
|
console.log("No empty, undefined, or NaN variables found");
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////
|
|
// BOARDS:
|
|
|
|
////////////////////////////////
|
|
// SAVE BOARD :: export diagrams + texts to the API
|
|
|
|
// Function to get all the vurrent data from the board.
|
|
function getCurrentBoardData(boardName, boardDesc) {
|
|
var apiUrlsLeft = [];
|
|
var apiUrlsCenter = [];
|
|
var apiUrlsRight = [];
|
|
document.querySelectorAll('#col-left div').forEach(function(element, index) {
|
|
var apiUrl = element.getAttribute('apiurl');
|
|
var id = element.id;
|
|
var order = apiUrlsLeft.length;
|
|
if (apiUrl === 'txt') {
|
|
var clone = element.cloneNode(true);
|
|
clone.querySelectorAll('span').forEach(function(span) {
|
|
span.parentNode.removeChild(span);
|
|
});
|
|
var innerText = clone.innerText.trim();
|
|
apiUrlsLeft.push({ order: order, id: id, text: innerText, api_url: null });
|
|
} else if (apiUrl) {
|
|
var h2Element = element.querySelector('h2');
|
|
text = h2Element ? h2Element.innerText.trim() : '';
|
|
apiUrlsLeft.push({ order: order, id: id, text: text, api_url: apiUrl });
|
|
}
|
|
});
|
|
|
|
document.querySelectorAll('#col-center div').forEach(function(element, index) {
|
|
var apiUrl = element.getAttribute('apiurl');
|
|
var id = element.id;
|
|
var text = element.innerHTML.trim(); // Assuming the text is the innerHTML of the element
|
|
var order = apiUrlsCenter.length;
|
|
if (apiUrl === 'txt') {
|
|
var clone = element.cloneNode(true);
|
|
clone.querySelectorAll('span').forEach(function(span) {
|
|
span.parentNode.removeChild(span);
|
|
});
|
|
var innerText = clone.innerText.trim();
|
|
apiUrlsCenter.push({ order: order, id: id, text: innerText, api_url: null });
|
|
} else if (apiUrl) {
|
|
var h2Element = element.querySelector('h2');
|
|
text = h2Element ? h2Element.myLen.trim() : '';
|
|
|
|
apiUrlsCenter.push({ order: order, id: id, text: text, api_url: apiUrl });
|
|
}
|
|
});
|
|
|
|
document.querySelectorAll('#col-right div').forEach(function(element, index) {
|
|
var apiUrl = element.getAttribute('apiurl');
|
|
var id = element.id;
|
|
var text = element.innerHTML.trim(); // Assuming the text is the innerHTML of the element
|
|
var order = apiUrlsRight.length;
|
|
if (apiUrl === 'txt') {
|
|
var clone = element.cloneNode(true);
|
|
clone.querySelectorAll('span').forEach(function(span) {
|
|
span.parentNode.removeChild(span);
|
|
});
|
|
var innerText = clone.innerText.trim();
|
|
apiUrlsRight.push({ order: order, id: id, text: innerText, api_url: null });
|
|
} else if (apiUrl) {
|
|
var h2Element = element.querySelector('h2');
|
|
text = h2Element ? h2Element.innerText.trim() : '';
|
|
apiUrlsRight.push({ order: order, id: id, text: text, api_url: apiUrl });
|
|
}
|
|
});
|
|
var result = {
|
|
// FIXME : boardName will be entered in the save board form
|
|
"Name" : boardName,
|
|
"Description": boardDesc,
|
|
"Hus": myParam,
|
|
"col-left": apiUrlsLeft,
|
|
"col-center": apiUrlsCenter,
|
|
"col-right": apiUrlsRight
|
|
};
|
|
//alert(JSON.stringify(result));
|
|
return sendDataToAPI(JSON.stringify(result));
|
|
}
|
|
|
|
|
|
// function to send and save the collected data from the current borad to the server thriugh the API
|
|
function sendDataToAPI(myJSON) {
|
|
const form = document.getElementById("saveBoardForm"); // Get the form element
|
|
const inputField = document.getElementById("boardName"); // Get the input field element
|
|
form.addEventListener("submit", function(event) {
|
|
if (inputField.value.trim() === "") { // Check if the input field is empty after trimming whitespace
|
|
event.preventDefault(); // Prevent form submission
|
|
console.log("Empty field detected. Form submission blocked.");
|
|
} else {
|
|
base_url_API = "http://localhost:5000";
|
|
const myApiUrl = base_url_API+"/boards/save"; // Replace with your API URL
|
|
const requestOptions = {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json"
|
|
},
|
|
body: myJSON //.stringify(myJSON)
|
|
};
|
|
fetch(myApiUrl, requestOptions)
|
|
//.then(response => response.json())
|
|
.then(data => {
|
|
console.log("Response from API:", myJSON);
|
|
// Handle the response from the API as needed
|
|
const myModal = new bootstrap.Modal(document.getElementById("saveBoardModal"));
|
|
const submitBtn = document.getElementById("submitBoard");
|
|
submitBtn.addEventListener("click", function() {
|
|
myModal.hide();
|
|
});
|
|
alert("Board saved!");
|
|
})
|
|
.catch(error => {
|
|
console.error("Error:", error);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
|
|
//function to prepare the tha API request upon a board JSON
|
|
function getFormId(api_url) {return api_url[0].toUpperCase() + api_url.split("/")[1][0].toUpperCase();};
|
|
|
|
////////////////////////////////
|
|
// LOAD BOARD
|
|
|
|
// button listener to get the BOARD LIST from the server
|
|
const buttonElement = document.getElementById("loadListBoardsButton");
|
|
buttonElement.addEventListener("click", function() {
|
|
fetchDataAndGenerateHTML(myParam);
|
|
});
|
|
|
|
|
|
// Function to generate the already loaded JSON data of a board.
|
|
async function loadBoards(myJSON) {
|
|
//myJSON = {"Name":"dddd","Description":" f fa af ffsdfsdfs fsd","Hus":"Charlie","col-left":[],"col-center":[{"order":0,"id":"grid-710994","text":"","api_url":"grid/weekly?hus=Charlie&sensor=Temp_MP1_1_Pos1&type=celsius&year=2023&week=23"}],"col-right":[]};
|
|
var cols = ["col-left", "col-center", "col-right"];
|
|
cols.forEach(col => {
|
|
if (col.length > 0) {
|
|
myJSON[col].forEach(async element => {
|
|
// Get necessary variables:
|
|
var myTitle = myTitle ? nuull : "";
|
|
var myColumn = col;
|
|
var myid = element.id;
|
|
var myTitle = element.text;
|
|
// Create DIV
|
|
await createMyDiv(myid, myColumn, myTitle);
|
|
|
|
// setTimeout(function() {
|
|
// console.log("After 2 seconds");
|
|
// // Code to be executed after the delay
|
|
// }, 2000);
|
|
|
|
// Check if is TXT or Diagram:
|
|
if (element.api_url != null ) {
|
|
var apiurl = element.api_url;
|
|
var formId = getFormId(apiurl);
|
|
var sensor = getValueByKeyFrrmUrl("sensor", apiurl);
|
|
var type = getValueByKeyFrrmUrl("type", apiurl);
|
|
var year = getValueByKeyFrrmUrl("year", apiurl);
|
|
|
|
switch (formId) {
|
|
case "PD":
|
|
var family = getValueByKeyFrrmUrl("family", apiurl);
|
|
var day = getValueByKeyFrrmUrl("day", apiurl);
|
|
// cl("PD LoadBoards:");
|
|
// cl(formId+" - "+apiurl+" - "+myid+" - "+myTitle+" - "+family+" - "+type+" - "+day);
|
|
makeApiRequest(formId, apiurl, myid, myTitle, family, type, day);
|
|
break;
|
|
|
|
case "GW":
|
|
var week = getValueByKeyFrrmUrl("week", apiurl);
|
|
// Trans for week and year to DD-MM-YYYY
|
|
var startingDate = getFormattedDateFromWeek(week, year);
|
|
//cl("startingDate: "+startingDate+" -formId - "+formId+" -apiurl: "+apiurl+" -myid: "+myid+" -myTitle: "+myTitle+" -semsor: "+sensor+" -type: "+type+" -week: "+week+" -year: "+year);
|
|
makeApiRequest(formId, apiurl, myid, myTitle, sensor, type, startingDate);
|
|
break;
|
|
case "GM":
|
|
var month = getValueByKeyFrrmUrl("month", apiurl);
|
|
makeApiRequest(formId, apiurl, myid, myTitle, sensor, type, year, month);
|
|
break;
|
|
case "GY":
|
|
makeApiRequest(formId, apiurl, myid, myTitle, sensor, type, year);;
|
|
break;
|
|
|
|
|
|
default:
|
|
makeApiRequest(formId, apiurl, myid);
|
|
break;
|
|
}
|
|
|
|
|
|
} else {
|
|
var formId = "TXT";
|
|
createTXT(myid, element.text);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
// function to load THE LIST OF boards FOR AN SPECIFIC hOUSE
|
|
function fetchDataAndGenerateHTML(husName) {
|
|
base_url_API = "http://localhost:5000";
|
|
var apiUrl = base_url_API+"/boards?hus="+husName;
|
|
cl(apiUrl);
|
|
fetch(apiUrl)
|
|
.then(response => response.json())
|
|
.then(jsonData => {
|
|
const generatedHTML = generateHTML(jsonData);
|
|
document.getElementById("loadBoard-pills-default").innerHTML = generatedHTML;
|
|
})
|
|
.catch(error => {
|
|
console.error("Error:", error);
|
|
});
|
|
}
|
|
|
|
// function to load one boards JSON data
|
|
function fetchOneBoardData(husName, boardFilename) {
|
|
var apiUrl = base_url_API+"/boards/load?hus="+myParam+"&filename="+boardFilename;
|
|
fetch(apiUrl)
|
|
.then(response => response.json())
|
|
.then(jsonData1 => {
|
|
loadBoards(jsonData1);
|
|
})
|
|
.catch(error => {
|
|
console.error("Error:", error);
|
|
});
|
|
}
|
|
|
|
|
|
function generateHTML(jsonData) {
|
|
let html = '<h2>List of saved boards. Just click the one you want to load.:</h2><p> :</p><ol>';
|
|
for (let i = 0; i < jsonData.length; i++) {
|
|
const item = jsonData[i];
|
|
html += `<li>${item.Name}</li>`;
|
|
html += '<ul>';
|
|
html += `<li>Description: ${item.Desc}</li>`;
|
|
html += '<p> </p>';
|
|
html += '<button onclick="fetchOneBoardData(\''+myParam+'\', \''+item.Filename+'\');" type="button" class="loadme btn btn-info"><i class="fa fa-bolt"></i>Load me!</button>';
|
|
html += '</ul>';
|
|
html += '<p> </p>';
|
|
}
|
|
html += '</ol>';
|
|
return html;
|
|
}
|
|
|
|
|
|
function checkElementExistence(myid, apiUrl) {
|
|
const element = document.getElementById(myid);
|
|
if (element) {
|
|
element.setAttribute("apiurl", apiUrl);
|
|
return
|
|
} else {
|
|
setTimeout(function() {
|
|
checkElementExistence(myid, apiUrl);
|
|
}, 1000); // Retry after 1 second (adjust the delay as needed)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////
|
|
// Server API:
|
|
// Define a function to make the API request
|
|
function makeApiRequest(formId, apiUrl, myid, myTitle, ...args) {
|
|
// Check the variables on the query:
|
|
// var myMissedVar = checkEmptyVariable(apiUrl);
|
|
// if(myMissedVar!=false) {
|
|
// alert("There are variables with null or NaN values. "+myMissedVar);
|
|
// return;
|
|
// };
|
|
|
|
const myArgs = [];
|
|
// Store all arguments in an array
|
|
myTitle = myTitle || ""; // Define emty if undefined:
|
|
myArgs.push(...args);
|
|
switch (formId) {
|
|
case "PD":
|
|
var family = myArgs[0];
|
|
var type = myArgs[1];
|
|
var day = myArgs[2];
|
|
|
|
break;
|
|
case "PW":
|
|
var family = myArgs[0];
|
|
var type = myArgs[1];
|
|
var year = myArgs[2];
|
|
var week = myArgs[3];
|
|
|
|
break;
|
|
case "PM":
|
|
var family = myArgs[0];
|
|
var type = myArgs[1];
|
|
var year = myArgs[2];
|
|
var month = myArgs[3];
|
|
|
|
break;
|
|
case "PY":
|
|
var family = myArgs[0];
|
|
var type = myArgs[1];
|
|
var year = myArgs[2];
|
|
|
|
break;
|
|
case "GW":
|
|
var sensor = myArgs[0];
|
|
var type = myArgs[1];
|
|
var inDate = myArgs[2];
|
|
|
|
break;
|
|
case "GM":
|
|
var sensor = myArgs[0];
|
|
var type = myArgs[1];
|
|
var year = myArgs[2];
|
|
var month = myArgs[3];
|
|
|
|
break;
|
|
case "GY":
|
|
var sensor = myArgs[0];
|
|
var type = myArgs[1];
|
|
var year = myArgs[2];
|
|
|
|
break;
|
|
case "HD":
|
|
var family = myArgs[0];
|
|
var type = myArgs[1];
|
|
var day = myArgs[2];
|
|
|
|
break;
|
|
case "HW":
|
|
var family = myArgs[0];
|
|
var type = myArgs[1];
|
|
var year = myArgs[2];
|
|
var week = myArgs[3];
|
|
|
|
break;
|
|
case "HM":
|
|
var family = myArgs[0];
|
|
var type = myArgs[1];
|
|
var year = myArgs[2];
|
|
var month = myArgs[3];
|
|
|
|
break;
|
|
case "HY":
|
|
var family = myArgs[0];
|
|
var type = myArgs[1];
|
|
var year = myArgs[2];
|
|
|
|
break;
|
|
}
|
|
|
|
// Make an AJAX request to your Flask API
|
|
|
|
var apiRequest = `http://localhost:5000/${apiUrl}`; ///parallel/daily?family=${family}&type=${type}&day=${day}`;
|
|
cl(apiRequest);
|
|
var myDiv_ = document.getElementById(myid);
|
|
var myCol = getName(formId[0]).split("-")[1];
|
|
//if (myDiv_ && myDiv_.hasAttribute("apiurl")) {
|
|
checkElementExistence(myid, apiUrl)
|
|
//document.getElementById(myid).setAttribute("apiurl", apiUrl);
|
|
//}
|
|
|
|
fetch(apiRequest)
|
|
.then((response) => {
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then((data) => {
|
|
// Format data for each diagram
|
|
switch (formId) {
|
|
case "PD":
|
|
case "PW":
|
|
case "PM":
|
|
case "PY":
|
|
const myxs = data.myxs;
|
|
const sample = data.sample;
|
|
var dataName = "apiRequest";
|
|
do_parallel("#"+myid, myxs, sample, myTitle);
|
|
break;
|
|
case "GW":
|
|
do_grid_weekly(myid, inDate, data[2]);
|
|
break;
|
|
case "GM":
|
|
do_grid_monthly(myid, month, year, data[2], apiUrl);
|
|
break;
|
|
case "GY":
|
|
do_grid_yearly(myid, year, data[2]);
|
|
break;
|
|
case "HD":
|
|
case "HW":
|
|
case "HM":
|
|
case "HY":
|
|
data.sensor_names.forEach(element => {
|
|
var dataRow = data[element]
|
|
do_horizon_row(dataRow, "#"+myid, element, data.days);
|
|
});
|
|
break;
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error("Fetch error:", error);
|
|
});
|
|
}
|
|
|
|
|