1##########################################################################
2# Copyright (c) 2025 - 2025 Altair Engineering Inc. All Rights Reserved
3# Contains trade secrets of Altair Engineering, Inc. Copyright notice
4# does not imply publication. Decompilation or disassembly of this
5# software is strictly prohibited.
6##########################################################################
7
8
9import alt.hst.gui.custom_plot as custom_plot
10import typing
11import os
12
13
14# -----------------------------------------------------------------------------
15# -----------------------------------------------------------------------------
16class HeatMap(custom_plot.CustomPlot):
17
18 # -------------------------------------------------------------------------
19 def __init__(self) -> None:
20 super().__init__()
21 self.setUpdateControlsEnabled(True, True)
22
23 # -------------------------------------------------------------------------
24 def makeChannelConfig(self) -> custom_plot.ChannelConfig:
25 config = custom_plot.ChannelConfig()
26 inputSection = config.addSection('Inputs')
27 inputSection.addTable('Variables', [custom_plot.ChannelTypes.VARIABLES],
28 allowMultiSelect=False)
29
30 outputSection = config.addSection('Outputs')
31 outputSection.addTable('Responses', [custom_plot.ChannelTypes.RESPONSES],
32 allowMultiSelect=True)
33
34 return config
35
36 # -------------------------------------------------------------------------
37 @classmethod
38 def getIcon(cls) -> str:
39 return os.path.join(os.path.dirname(__file__), 'heatmap.svg')
40
41 # -------------------------------------------------------------------------
42 @classmethod
43 def getThumbnail(cls) -> str:
44 return cls.getIcon()
45
46 # -------------------------------------------------------------------------
47 def getJsPolyfill(self) -> str:
48 # This seems to be a known issue with plotly.js sometimes for heatmaps.
49 # We can work around it by reloading the page once after the first load.
50 return '''
51 function reloadOnce() {
52 if (sessionStorage.getItem("loadedOnce") !== "true") {
53 location.reload();
54 console.log("Reloading page once");
55 sessionStorage.setItem("loadedOnce", "true");
56 }
57 }
58 '''
59
60 # -------------------------------------------------------------------------
61 def getEventSetupScript(self) -> str:
62 script = """
63 var pointNumber='', curveNumber='', colors=[];
64 for(var i=0; i < data.points.length; i++){
65 pointNumber = data.points[i].pointNumber;
66 curveNumber = data.points[i].curveNumber;
67 };
68 if (typeof pointNumber[1] === 'number' && pointNumber[1] >= 0) {
69 hst_plot.handlePointClickEvent(pointNumber[1], curveNumber);
70 }
71 """
72 clickHandler = self.createEventHandler('plot', 'plotly_click', script)
73 return """
74 var plot = document.querySelector(".plotly-graph-div");
75 reloadOnce();
76 if (plot === null) {
77 console.log("Plotly graph object not found");
78 } else {""" + clickHandler + "}"
79
80 # -------------------------------------------------------------------------
81 def plot(self, channelSelection: custom_plot.ChannelSelection) -> typing.Tuple[custom_plot.PlotOutputType, str]:
82 import plotly # type: ignore[import-untyped]
83 import plotly.graph_objects as go # type: ignore[import-untyped]
84
85 variables = channelSelection.getItems('Inputs', 'Variables')
86 if not variables:
87 return custom_plot.PlotOutputType.HTML_STRING, ''
88
89 variableLabel = variables[0].getLabel()
90 dataset = self.getDataSet()
91
92 z = []
93 text = []
94 responseLabels = []
95 responses = channelSelection.getItems('Outputs', 'Responses')
96 for response in responses:
97 z.append(dataset.getStoredEvaluationValues(response.getVarname()))
98 # String variables need to be converted to their corresponding strings
99 text.append(list(map(str, dataset.getEvaluationValues(response.getVarname()))))
100 responseLabels.append(response.getLabel())
101
102 fig = go.Figure(data=go.Heatmap(
103 z=z,
104 x=[n for n in range(1, len(z[0]) + 1)],
105 y=responseLabels,
106 text=text,
107 colorscale='Turbo',
108 ))
109
110 fig.update_layout(
111 title=dict(text=f'Evaluation Data Heatmap - {variableLabel}'),
112 xaxis_title='Evaluation',
113 yaxis_title='Output',
114 autosize=True,
115 modebar_remove=['toImage'],
116 )
117
118 return custom_plot.PlotOutputType.HTML_FILE, str(plotly.offline.plot(
119 fig, include_plotlyjs=True, output_type='file',
120 filename=self.getPlotOutputFile(), auto_open=False))
121
122
123# -----------------------------------------------------------------------------
124def getPlotClass() -> typing.Type[custom_plot.CustomPlot]:
125 return HeatMap