Example 04 - Create a Heatmap plot for post-processing#

  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