import Plotly from 'plotly.js-dist';
import { displayMessage } from '../../../utils.js';
import { Sidebar } from "../../../../ui/sidebar/Sidebar.js";
import { PlotlyVefPlot } from '../../../../plot/templates/PlotlyVefPlot.js'
import { Window } from '../../../../ui/Window.js';
import "./staOverview.css";
import { displayImage } from '../media.js';
let vefPlotly = null;
let plotbar = null;
let resizeObserver = null;
let infoWindow = null;
function filterDatastreams(datastreams, sensor, observedProperty) {
const result = {
sensors: {},
observedProperties: {},
datastreams: {},
selectedSensor: sensor,
selectedObservedProperty: observedProperty
}
datastreams.forEach(ds => {
if ("Sensor" in ds) result.sensors[ds.Sensor["@iot.id"]] = ds.Sensor;
if ((sensor.length == 0) && (observedProperty.length == 0)) {
if ("ObservedProperty" in ds) result.observedProperties[ds.ObservedProperty["@iot.id"]] = ds.ObservedProperty;
result.datastreams[ds["@iot.id"]] = ds;
} else if ((sensor.length > 0) && (observedProperty.length == 0)) {
if (ds.Sensor["@iot.id"] == sensor) {
if ("ObservedProperty" in ds) result.observedProperties[ds.ObservedProperty["@iot.id"]] = ds.ObservedProperty;
result.datastreams[ds["@iot.id"]] = ds;
}
} else if ((sensor.length > 0) && (observedProperty.length > 0)) {
if (ds.Sensor["@iot.id"] == sensor) {
result.observedProperties[ds.ObservedProperty["@iot.id"]] = ds.ObservedProperty;
if (ds.ObservedProperty["@iot.id"] == observedProperty) result.datastreams[ds["@iot.id"]] = ds;
}
} else if ((sensor.length == 0) && (observedProperty.length > 0)) {
result.observedProperties[ds.ObservedProperty["@iot.id"]] = ds.ObservedProperty;
if (ds.ObservedProperty["@iot.id"] == observedProperty) result.datastreams[ds["@iot.id"]] = ds;
if ("Sensor" in ds) result.selectedSensor = ds.Sensor["@iot.id"];
}
});
if (Object.keys(result.observedProperties).length == 1) {
result.selectedObservedProperty = result.observedProperties[Object.keys(result.observedProperties)[0]]["@iot.id"];
}
return result;
}
function showPlotbar(title) {
if (!plotbar) {
plotbar = new Sidebar(null, {
position: "bottom",
menu: false,
closeable: true,
responsive: true
});
plotbar.addGroup("plot-content");
plotbar.on("close", () => {
if (vefPlotly) Plotly.purge(vefPlotly.graphDiv);
});
resizeObserver = new ResizeObserver(() => {
if (vefPlotly) Plotly.Plots.resize(vefPlotly.graphDiv);
});
resizeObserver.observe(plotbar.getGroupContainer("plot-content"));
}
const container = plotbar.getGroupContainer("plot-content");
container.style.overflow = "hidden";
container.innerHTML = `
<div style="text-align: center; margin: 20px 0">
<i style="color:var(--primary-color); font-size: 18px;" class="fas fa-spinner fa-spin fa-fw"></i>'
</div>
`;
let target = document.querySelector(".vef-ui.viewer");
if (!target) target = document.body;
plotbar.appendTo(target);
plotbar.setTitle("plot-content", title);
plotbar.show("plot-content");
}
function displayDatastreamInfo(infoButton, datastream) {
if (!infoWindow) {
infoWindow = new Window(null, {
width: "400px",
height: "unset",
open: false,
mode: "slim",
title: null
});
let windowElement = infoWindow.getElement();
windowElement.classList.add("pointer-left");
windowElement.style.pointerEvents = "none";
windowElement.querySelector(".fas.fa-times").style.display = "none";
}
let rect = infoButton.getBoundingClientRect();
infoWindow.setOptions({
top: (rect.top - 3) + "px",
left: (rect.left + rect.width + 15) + "px",
open: true,
content: `
<div><b>The latest 1000 measurements are displayed</b></div><br>
<div>${datastream?.description || ""}<div>
`
});
}
function displayPlot(observations, datastream) {
const container = plotbar.getGroupContainer("plot-content");
container.style.overflow = "hidden";
if (observations.length == 0) {
container.innerHTML = "<b style='font-size: 16px;'>This datastream does not have any data.</b>";
return;
} else {
container.innerHTML = ``;
}
const data = { x: [], y: [] };
observations.forEach(observation => {
data.x.push(observation.resultTime || observation.phenomenonTime);
data.y.push(observation.result);
});
const layout = {
xaxis: { title: { text: "Time" } },
yaxis: { title: { text: `${datastream?.unitOfMeasurement?.name} [${datastream?.unitOfMeasurement?.symbol}]` } },
autosize: true,
height: 400
};
if (vefPlotly) Plotly.purge(vefPlotly.graphDiv);
vefPlotly = new PlotlyVefPlot(container, [data], layout);
Plotly.newPlot(vefPlotly.graphDiv, vefPlotly.data, vefPlotly.layout, vefPlotly.config);
vefPlotly.extensions.apply({ 'redirectNotification': { 'callback': displayMessage } });
const infoButton = plotbar.getElement().querySelector(".sta-ds-info-button");
infoButton.addEventListener("mouseover", () => displayDatastreamInfo(infoButton, datastream));
infoButton.addEventListener("mouseleave", () => infoWindow.close());
}
/**
*
* @param {object} datastreams
* @param {Layer} layer
* @returns {string} div placeholder
*/
export function staOverview(datastreams, layer) {
if (!datastreams) return undefined;
const div = document.createElement("div");
div.classList.add("sta-overview");
let filtered = filterDatastreams(datastreams, "", "");
const create = () => {
const sensors = filtered.sensors;
const observedProperties = filtered.observedProperties;
div.innerHTML = "";
const sensorSelect = document.createElement("select");
let option = document.createElement("option");
option.value = "";
option.innerText = "-- select a sensor --";
sensorSelect.appendChild(option);
for (let id in sensors) {
option = document.createElement("option");
option.value = id;
option.innerText = sensors[id].name;
sensorSelect.appendChild(option);
}
sensorSelect.value = filtered.selectedSensor;
sensorSelect.addEventListener("change", () => {
filtered = filterDatastreams(datastreams, sensorSelect.value, "");
create();
});
div.appendChild(sensorSelect);
const observedPropertySelect = document.createElement("select");
option = document.createElement("option");
option.value = "";
option.innerText = "-- select an observed property --";
observedPropertySelect.appendChild(option);
for (let id in observedProperties) {
option = document.createElement("option");
option.value = id;
option.innerText = observedProperties[id].name;
observedPropertySelect.appendChild(option);
}
observedPropertySelect.value = filtered.selectedObservedProperty;
observedPropertySelect.addEventListener("change", () => {
filtered = filterDatastreams(datastreams, sensorSelect.value, observedPropertySelect.value);
create();
});
div.appendChild(observedPropertySelect);
const dsKeys = Object.keys(filtered.datastreams);
if (dsKeys.length == 1) {
const button = document.createElement("button");
button.innerText = "Visualize Time Series";
button.addEventListener("click", () => {
const id = dsKeys[0];
showPlotbar("<i title='' class='sta-ds-info-button fas fa-info'></i>" + filtered.datastreams[id].name);
layer.getObservations(filtered.datastreams[id]["Observations@iot.navigationLink"]).then(observations => {
displayPlot(observations, filtered.datastreams[id]);
});
});
div.appendChild(button);
} else {
const datastreamPropertySelect = document.createElement("select");
option = document.createElement("option");
option.value = "";
option.innerText = "-- select a datastream --";
datastreamPropertySelect.appendChild(option);
for (let id in filtered.datastreams) {
option = document.createElement("option");
option.value = id;
option.innerText = filtered.datastreams[id].name;
datastreamPropertySelect.appendChild(option);
}
datastreamPropertySelect.value = "";
const button = document.createElement("button");
button.innerText = "Visualize Time Series";
button.addEventListener("click", () => {
const id = datastreamPropertySelect.value;
showPlotbar("<i title='' class='sta-ds-info-button fas fa-info'></i>" + filtered.datastreams[id].name);
layer.getObservations(filtered.datastreams[id]["Observations@iot.navigationLink"]).then(observations => {
displayPlot(observations, filtered.datastreams[id]);
});
});
datastreamPropertySelect.addEventListener("change", () => {
const value = datastreamPropertySelect.value;
if (value.length == 0) {
button.remove()
} else {
div.appendChild(button);
}
});
div.appendChild(datastreamPropertySelect);
}
};
create();
return div;
}
/**
* Generates a div element containing images and their captions.
*
* @param {Array} images - An array of image objects.
* @param {string} images[].url - The URL of the image.
* @param {string} images[].caption - The caption for the image.
* @returns {HTMLDivElement|undefined} A div element containing the images and captions, or undefined if no images are provided.
*/
export function staThingImages(images) {
if (!images) return undefined;
const div = document.createElement("div");
for (let i = 0; i < images.length; ++i) {
const caption = document.createElement("div");
caption.innerText = images[i].caption;
div.appendChild(caption);
const img = displayImage(images[i].url);
img.style.marginBottom = "5px";
div.appendChild(img);
}
return div;
}