2020-05-25 21:30:55 -05:00
|
|
|
import * as Plotly from "plotly.js-cartesian-dist-min";
|
|
|
|
import dayjs from "dayjs";
|
|
|
|
import {
|
|
|
|
ChartSettings,
|
|
|
|
DataPayload,
|
|
|
|
DisplaySettings,
|
|
|
|
FontSettings,
|
|
|
|
HeatmapCaptions,
|
|
|
|
HeatmapData,
|
|
|
|
LayoutSettings,
|
|
|
|
PartitionTimeStampToData,
|
2021-01-20 09:15:01 -06:00
|
|
|
PortalTheme,
|
2020-05-25 21:30:55 -05:00
|
|
|
} from "./HeatmapDatatypes";
|
|
|
|
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
|
2020-07-24 16:45:48 -05:00
|
|
|
import { sendCachedDataMessage, sendMessage } from "../../Common/MessageHandler";
|
2020-05-25 21:30:55 -05:00
|
|
|
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
|
|
|
import { StyleConstants } from "../../Common/Constants";
|
|
|
|
import "./Heatmap.less";
|
|
|
|
|
|
|
|
export class Heatmap {
|
|
|
|
public static readonly elementId: string = "heatmap";
|
|
|
|
|
|
|
|
private _chartData: HeatmapData;
|
|
|
|
private _heatmapCaptions: HeatmapCaptions;
|
|
|
|
private _theme: PortalTheme;
|
|
|
|
private _defaultFontColor: string;
|
|
|
|
|
|
|
|
constructor(data: DataPayload, heatmapCaptions: HeatmapCaptions, theme: PortalTheme) {
|
|
|
|
this._theme = theme;
|
|
|
|
this._defaultFontColor = StyleConstants.BaseDark;
|
|
|
|
this._setThemeColorForChart();
|
|
|
|
this._chartData = this.generateMatrixFromMap(data);
|
|
|
|
this._heatmapCaptions = heatmapCaptions;
|
|
|
|
}
|
|
|
|
|
|
|
|
private _setThemeColorForChart() {
|
|
|
|
if (isDarkTheme(this._theme)) {
|
|
|
|
this._defaultFontColor = StyleConstants.BaseLight;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private _getFontStyles(size: number = StyleConstants.MediumFontSize, color: string = "#838383"): FontSettings {
|
|
|
|
return {
|
|
|
|
family: StyleConstants.DataExplorerFont,
|
|
|
|
size,
|
2021-01-20 09:15:01 -06:00
|
|
|
color,
|
2020-05-25 21:30:55 -05:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
public generateMatrixFromMap(data: DataPayload): HeatmapData {
|
|
|
|
// all keys in data payload, sorted...
|
|
|
|
const rows: string[] = Object.keys(data).sort((a: string, b: string) => {
|
|
|
|
if (parseInt(a) < parseInt(b)) {
|
|
|
|
return -1;
|
|
|
|
} else {
|
|
|
|
if (parseInt(a) > parseInt(b)) {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
const output: HeatmapData = {
|
|
|
|
yAxisPoints: [],
|
|
|
|
dataPoints: [],
|
|
|
|
xAxisPoints: Object.keys(data[rows[0]]).sort((a: string, b: string) => {
|
|
|
|
if (a < b) {
|
|
|
|
return -1;
|
|
|
|
} else {
|
|
|
|
if (a > b) {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
2021-01-20 09:15:01 -06:00
|
|
|
}),
|
2020-05-25 21:30:55 -05:00
|
|
|
};
|
|
|
|
// go thru all rows and create 2d matrix for heatmap...
|
|
|
|
for (let i = 0; i < rows.length; i++) {
|
|
|
|
output.yAxisPoints.push(rows[i]);
|
|
|
|
let dataPoints: number[] = [];
|
|
|
|
for (let a = 0; a < output.xAxisPoints.length; a++) {
|
|
|
|
let row: PartitionTimeStampToData = data[rows[i]];
|
|
|
|
dataPoints.push(row[output.xAxisPoints[a]]["Normalized Throughput"]);
|
|
|
|
}
|
|
|
|
output.dataPoints.push(dataPoints);
|
|
|
|
}
|
|
|
|
for (let a = 0; a < output.xAxisPoints.length; a++) {
|
|
|
|
const dateTime = output.xAxisPoints[a];
|
|
|
|
// convert to local users timezone...
|
|
|
|
const day = dayjs(new Date(dateTime)).format("YYYY-MM-DD");
|
|
|
|
const hour = dayjs(new Date(dateTime)).format("HH:mm:ss");
|
|
|
|
// coerce to ISOString format since that is what plotly wants...
|
|
|
|
output.xAxisPoints[a] = `${day}T${hour}Z`;
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
private _getChartSettings(): ChartSettings[] {
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
z: this._chartData.dataPoints,
|
|
|
|
type: "heatmap",
|
|
|
|
zmin: 0,
|
|
|
|
zmid: 50,
|
|
|
|
zmax: 100,
|
|
|
|
colorscale: [
|
|
|
|
[0.0, "#1FD338"],
|
|
|
|
[0.1, "#1CAD2F"],
|
|
|
|
[0.2, "#50A527"],
|
|
|
|
[0.3, "#719F21"],
|
|
|
|
[0.4, "#95991B"],
|
|
|
|
[0.5, "#CE8F11"],
|
|
|
|
[0.6, "#E27F0F"],
|
|
|
|
[0.7, "#E46612"],
|
|
|
|
[0.8, "#E64914"],
|
|
|
|
[0.9, "#B80016"],
|
2021-01-20 09:15:01 -06:00
|
|
|
[1.0, "#B80016"],
|
2020-05-25 21:30:55 -05:00
|
|
|
],
|
|
|
|
name: "",
|
|
|
|
hovertemplate: this._heatmapCaptions.tooltipText,
|
|
|
|
colorbar: {
|
|
|
|
thickness: 15,
|
|
|
|
outlinewidth: 0,
|
|
|
|
tickcolor: StyleConstants.BaseDark,
|
2021-01-20 09:15:01 -06:00
|
|
|
tickfont: this._getFontStyles(10, this._defaultFontColor),
|
2020-05-25 21:30:55 -05:00
|
|
|
},
|
|
|
|
y: this._chartData.yAxisPoints,
|
2021-01-20 09:15:01 -06:00
|
|
|
x: this._chartData.xAxisPoints,
|
|
|
|
},
|
2020-05-25 21:30:55 -05:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
private _getLayoutSettings(): LayoutSettings {
|
|
|
|
return {
|
|
|
|
margin: {
|
|
|
|
l: 40,
|
|
|
|
r: 10,
|
|
|
|
b: 35,
|
|
|
|
t: 30,
|
2021-01-20 09:15:01 -06:00
|
|
|
pad: 0,
|
2020-05-25 21:30:55 -05:00
|
|
|
},
|
|
|
|
paper_bgcolor: "transparent",
|
|
|
|
plot_bgcolor: "transparent",
|
|
|
|
width: 462,
|
|
|
|
height: 240,
|
|
|
|
yaxis: {
|
|
|
|
title: this._heatmapCaptions.yAxisTitle,
|
|
|
|
titlefont: this._getFontStyles(11),
|
|
|
|
autorange: true,
|
|
|
|
showgrid: false,
|
|
|
|
zeroline: false,
|
|
|
|
showline: false,
|
|
|
|
autotick: true,
|
|
|
|
fixedrange: true,
|
|
|
|
ticks: "",
|
2021-01-20 09:15:01 -06:00
|
|
|
showticklabels: false,
|
2020-05-25 21:30:55 -05:00
|
|
|
},
|
|
|
|
xaxis: {
|
|
|
|
fixedrange: true,
|
|
|
|
title: "*White area in heatmap indicates there is no available data",
|
|
|
|
titlefont: this._getFontStyles(11),
|
|
|
|
autorange: true,
|
|
|
|
showgrid: false,
|
|
|
|
zeroline: false,
|
|
|
|
showline: false,
|
|
|
|
autotick: true,
|
|
|
|
tickformat: this._heatmapCaptions.timeWindow > 7 ? "%I:%M %p" : "%b %e",
|
|
|
|
showticklabels: true,
|
2021-01-20 09:15:01 -06:00
|
|
|
tickfont: this._getFontStyles(10),
|
2020-05-25 21:30:55 -05:00
|
|
|
},
|
|
|
|
title: {
|
|
|
|
text: this._heatmapCaptions.chartTitle,
|
|
|
|
x: 0.01,
|
2021-01-20 09:15:01 -06:00
|
|
|
font: this._getFontStyles(13, this._defaultFontColor),
|
|
|
|
},
|
2020-05-25 21:30:55 -05:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private _getChartDisplaySettings(): DisplaySettings {
|
|
|
|
return {
|
|
|
|
/* heatmap can be fully responsive however the min-height needed in that case is greater than the iframe portal height, hence explicit width + height have been set in _getLayoutSettings
|
|
|
|
responsive: true,*/
|
2021-01-20 09:15:01 -06:00
|
|
|
displayModeBar: false,
|
2020-05-25 21:30:55 -05:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
public drawHeatmap(): void {
|
|
|
|
// todo - create random elementId generator so multiple heatmaps can be created - ticket # 431469
|
|
|
|
Plotly.plot(
|
|
|
|
Heatmap.elementId,
|
|
|
|
this._getChartSettings(),
|
|
|
|
this._getLayoutSettings(),
|
|
|
|
this._getChartDisplaySettings()
|
|
|
|
);
|
|
|
|
let plotDiv: any = document.getElementById(Heatmap.elementId);
|
|
|
|
plotDiv.on("plotly_click", (data: any) => {
|
|
|
|
let timeSelected: string = data.points[0].x;
|
|
|
|
timeSelected = timeSelected.replace(" ", "T");
|
|
|
|
timeSelected = `${timeSelected}Z`;
|
2020-07-06 17:16:24 +02:00
|
|
|
let xAxisIndex = 0;
|
2020-05-25 21:30:55 -05:00
|
|
|
for (let i = 0; i < this._chartData.xAxisPoints.length; i++) {
|
|
|
|
if (this._chartData.xAxisPoints[i] === timeSelected) {
|
|
|
|
xAxisIndex = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let output = [];
|
|
|
|
for (let i = 0; i < this._chartData.dataPoints.length; i++) {
|
|
|
|
output.push(this._chartData.dataPoints[i][xAxisIndex]);
|
|
|
|
}
|
2020-07-24 16:45:48 -05:00
|
|
|
sendCachedDataMessage(MessageTypes.LogInfo, output);
|
2020-05-25 21:30:55 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isDarkTheme(theme: PortalTheme) {
|
|
|
|
return theme === PortalTheme.dark;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function handleMessage(event: MessageEvent) {
|
|
|
|
if (isInvalidParentFrameOrigin(event)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof event.data !== "object" || event.data["signature"] !== "pcIframe") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
typeof event.data.data !== "object" ||
|
|
|
|
!("chartData" in event.data.data) ||
|
|
|
|
!("chartSettings" in event.data.data)
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Plotly.purge(Heatmap.elementId);
|
2020-07-06 17:16:24 +02:00
|
|
|
|
|
|
|
document.getElementById(Heatmap.elementId)!.innerHTML = "";
|
2020-05-25 21:30:55 -05:00
|
|
|
const data = event.data.data;
|
|
|
|
const chartData: DataPayload = data.chartData;
|
|
|
|
const chartSettings: HeatmapCaptions = data.chartSettings;
|
|
|
|
const chartTheme: PortalTheme = data.theme;
|
|
|
|
if (Object.keys(chartData).length) {
|
|
|
|
new Heatmap(chartData, chartSettings, chartTheme).drawHeatmap();
|
|
|
|
} else {
|
|
|
|
const chartTitleElement = document.createElement("div");
|
|
|
|
chartTitleElement.innerHTML = data.chartSettings.chartTitle;
|
|
|
|
chartTitleElement.classList.add("chartTitle");
|
|
|
|
|
|
|
|
const noDataMessageElement = document.createElement("div");
|
|
|
|
noDataMessageElement.classList.add("noDataMessage");
|
|
|
|
const noDataMessageContent = document.createElement("div");
|
|
|
|
noDataMessageContent.innerHTML = data.errorMessage;
|
|
|
|
|
|
|
|
noDataMessageElement.appendChild(noDataMessageContent);
|
|
|
|
|
|
|
|
if (isDarkTheme(chartTheme)) {
|
|
|
|
chartTitleElement.classList.add("dark-theme");
|
|
|
|
noDataMessageElement.classList.add("dark-theme");
|
|
|
|
noDataMessageContent.classList.add("dark-theme");
|
|
|
|
}
|
|
|
|
|
2020-07-06 17:16:24 +02:00
|
|
|
document.getElementById(Heatmap.elementId)!.appendChild(chartTitleElement);
|
|
|
|
document.getElementById(Heatmap.elementId)!.appendChild(noDataMessageElement);
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
window.addEventListener("message", handleMessage, false);
|
2020-07-24 16:45:48 -05:00
|
|
|
sendMessage("ready");
|