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.
 
 
 
 
infravis-trahust/web-html/js/dash.js

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>&nbsp:</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>&nbsp;</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>&nbsp;</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);
});
}