import Plotly from 'plotly.js-dist';
import { filterDataFrame } from '../../../../data/utils.js';
import "./measurementsLayerOverview.css";
// static variables for keeping the plot type
let modeIndex = 0;
const plotModes = ["markers", "lines", "lines+markers"]
// static plot container
let plotInterval = null;
let plotTimeout = null;
function clearTimers() {
if (plotInterval) clearInterval(plotInterval);
if (plotTimeout) clearTimeout(plotTimeout);
plotInterval = null;
plotTimeout = null;
}
/**
* Render the plot on the given HTMLElement.
* Also used for re rendering, when the axis changes
*
* @param {HTMLElement} container (optional)
* @param {string} labelX label of x column
* @param {string} labelY label of y column
* @param {object[]} data
* @param {boolean} invertXAxis
* @param {boolean} invertYAxis
*
* @memberof vef.plot
* @returns {HTMLElement} container
*/
function renderPlot(container, labelX, labelY, data, invertXAxis, invertYAxis, hoverY) {
if (!container) container = document.createElement("div");
container.classList.add("vef-plot");
for (let i = 0; i < data.length; ++i) {
if (modeIndex >= plotModes.length) modeIndex = 0;
data[i].mode = plotModes[modeIndex];
data[i].type = "scattergl";
}
Plotly.purge(container);
Plotly.newPlot(container, data, {
spikedistance: -1,
hovermode: (hoverY) ? "y" : "x",
xaxis: { title: { text: labelX }, showspikes: true, spikethickness: 2, autorange: (invertXAxis) ? "reversed" : true },
yaxis: { title: { text: labelY }, showspikes: true, spikethickness: 2, autorange: (invertYAxis) ? "reversed" : true },
margin: {
l: 60,
r: 10,
b: 50,
t: 80,
pad: 0
}
}, {
displayModeBar: true,
displaylogo: false,
responsive: true,
modeBarButtonsToRemove: ["select2d", "lasso2d", "hoverClosestGl2d", "hoverClosestPie", "toggleHover", "sendDataToCloud", "toggleSpikelines", "hoverClosestCartesian", "hoverCompareCartesian", "autoScale2d"],
modeBarButtonsToAdd: [
{
name: 'Change Mode',
icon: () => {
const icon = document.createElement("i");
icon.classList.add("fas", "fa-project-diagram");
return icon;
},
click: gd => {
modeIndex = (modeIndex + 1) % plotModes.length;
Plotly.restyle(gd, 'mode', plotModes[modeIndex])
}
}
]
});
return container;
}
/**
* Render the plot on the given HTMLElement.
* Also used for re rendering, when the axis changes
*
* @param {HTMLElement} container (optional)
* @param {DataFrame} dataFrame
* @param {number} iX index of x column
* @param {string} labelX label of x column
* @param {number} iY index of y column
* @param {string} labelY label of y column
* @param {boolean} invertXAxis
* @param {boolean} invertYAxis
* @param {boolean} hoverY
*
* @memberof vef.plot
* @returns {HTMLElement} container
*/
function renderDataframePlot(container, dataFrame, iX, labelX, iY, labelY, invertXAxis, invertYAxis, hoverY) {
const dataX = dataFrame.series[iX];
const dataY = dataFrame.series[iY];
return renderPlot(container, labelX, labelY, [{ x: dataX, y: dataY }], invertXAxis, invertYAxis, hoverY);
}
/**
* Create the content for the visualizazion of plots in the sidebar
*
* @param {HTMLElement} content container element
* @param {object} event "visualize" event of the MeasurementsLayer
* @param {DataFrame} dataFrame
* @param {string} options {groupBy, defaultYAxis, invertYAxis, defaultXAxis, invertXAxis, hiddenColumns}
*
* @memberof vef.plot
* @returns {HTMLElement} content
*/
function createPlotSidebar(content, event, dataFrame, options) {
if (!content) content = document.createElement("div");
let invertYAxis = options.invertYAxis;
let invertXAxis = options.invertXAxis;
content.classList.add("plot-sidebar");
content.innerHTML = `
<h3>Diagram</h3>
<div class="plot"></div>
<h3>Axis Selection</h3>
<div class="axis-select-container">
<label class="axis-select-label">Y-Axis:</label><select class="axis-select axis-y"></select>
</div>
<div class="axis-select-container">
<label class="axis-select-label">X-Axis:</label><select class="axis-select axis-x"></select>
</div>
<a class="invert-y-axis" href="#"><i class="fas fa-exchange-alt"></i> Invert Y-Axis</a>
<a class="invert-x-axis" href="#"><i class="fas fa-exchange-alt"></i> Invert X-Axis</a>
`;
// filter
let value = null;
if (options.groupBy in dataFrame.columnMap) {
value = event.row[dataFrame.columnMap[options.groupBy]];
dataFrame = filterDataFrame(dataFrame, options.groupBy, value);
}
// get axis inidices
let x, y;
if (options.defaultXAxis) x = event.layer.getColumnIndex(dataFrame, options.defaultXAxis);
if (options.defaultYAxis) y = event.layer.getColumnIndex(dataFrame, options.defaultYAxis);
if (!Number.isFinite(x)) x = event.layer.getColumnIndex(dataFrame, event.layer.columnData) || 0;
if (!Number.isFinite(y)) y = event.layer.getColumnIndex(dataFrame, event.layer.columnData) || 0;
let labelX = event.layer.getColumnName(dataFrame, x);
let labelY = event.layer.getColumnName(dataFrame, y);
// render plot
const plotContainer = content.querySelector(".plot");
const plot = () => renderDataframePlot(plotContainer, dataFrame, x, labelX, y, labelY, invertXAxis, invertYAxis);
plot();
// invert y-axis button
const invertYButton = content.querySelector(".invert-y-axis");
invertYButton.addEventListener("click", (e) => {
e.preventDefault();
invertYAxis = !invertYAxis;
plot();
});
// invert x-axis button
const invertXButton = content.querySelector(".invert-x-axis");
invertXButton.addEventListener("click", (e) => {
e.preventDefault();
invertXAxis = !invertXAxis;
plot();
});
// axis selection
let selectX = content.querySelector(".axis-x");
let selectY = content.querySelector(".axis-y");
for (let i in dataFrame.columnMap) {
if (options.hiddenColumns && options.hiddenColumns.includes(i)) continue;
const html = `<option value="${i}">${i}</option>`;
selectX.innerHTML += html;
selectY.innerHTML += html;
}
selectX.value = labelX;
selectY.value = labelY;
selectX.addEventListener("change", () => {
labelX = selectX.value;
x = event.layer.getColumnIndex(dataFrame, labelX);
plot();
});
selectY.addEventListener("change", () => {
labelY = selectY.value;
y = event.layer.getColumnIndex(dataFrame, labelY);
plot();
});
return content;
}
function plot(target, layer, data) {
clearTimers();
const createPlot = () => {
clearTimers();
createPlotSidebar(target, {
layer: layer,
row: data || {}
}, layer.dataFrameOriginal, {
defaultYAxis: layer.defaultYAxis,
invertYAxis: layer.invertYAxis,
defaultXAxis: layer.defaultXAxis,
invertXAxis: layer.invertXAxis,
groupBy: layer.groupByColumn,
hiddenColumns: layer.hiddenColumns
});
if (layer.type == "dws") {
target.insertAdjacentHTML("afterbegin", `
<h3>PLEASE NOTE</h3>
<div>Data is aggregated by <b>${layer.aggregate}</b> and limited to <b>${layer.limit}</b> points for performance reasons!</div>
`);
}
};
// only init plot if the target is visible in DOM (offsetParent == null if not displayed)
// use timeout to push to end of eventloop. If not in DOM check repeatedly
plotTimeout = setTimeout(() => {
if (target.offsetParent) {
createPlot()
} else {
plotInterval = setInterval(() => {
if (target.offsetParent) createPlot();
}, 700);
}
}, 50);
}
/**
* Creates a container element and plots data from MeasurementsLayer
*
* @param {Object} layer - The layer object to be used for plotting.
* @param {Object} data - The data to be plotted in the container.
* @returns {HTMLDivElement} Plot Container
*/
export function measurementsLayerOverview(layer, data) {
const container = document.createElement("div");
container.addEventListener("append", () => {
plot(container, layer, data);
});
return container;
}