[SOLVED] slow browser rendering of plotly heatmap

Issue

I’m rendering a 212 row x 64 column DF of ints (final_df) ranging from 0 to 6 as an (annotation-less) plotly annotated heatmap. I’m doin this in my browser (microsoft edge) using a file from fig.write_html(). The final heatmap renders very slowly in my browser, to the extent I’m getting ‘page not responding’ warnings, and any zooming in/out of the graph is also very slow. This is surprising given the df is not that big.

Can anyone suggest why this is and how to speed it up?

Thanks,
Tim

def discrete_colorscale(bvals, colors):
    #https://chart-studio.plotly.com/~empet/15229/heatmap-with-a-discrete-colorscale/#/
    """
    bvals - list of values bounding intervals/ranges of interest
    colors - list of rgb or hex colorcodes for values in [bvals[k], bvals[k+1]],0<=k < len(bvals)-1
    returns the plotly  discrete colorscale
    """
    if len(bvals) != len(colors)+1:
        raise ValueError('len(boundary values) should be equal to  len(colors)+1')
    bvals = sorted(bvals)     
    nvals = [(v-bvals[0])/(bvals[-1]-bvals[0]) for v in bvals]  #normalized values
    
    dcolorscale = [] #discrete colorscale
    for k in range(len(colors)):
        dcolorscale.extend([[nvals[k], colors[k]], [nvals[k+1], colors[k]]])
    return dcolorscale


#final_df is a 212 row x 64 col df of ints ranging from 0 to 6
#cell_df is an empty 212x64 df of empty strings to remove cell labelling behaviour
cell_df = final_df.applymap(lambda x: annot_map.get(x, x)) 
cell_labels = cell_df.values.tolist()
bvals = [0,1,2,3,4,5,6,7]

colors_map = ['rgb(244,244,255)', #whiteish 
              'rgb(255, 128, 0)', #orange 
              'rgb(255,0,0)', #red 
              'rgb(0, 0, 255)', #blue 
              'rgb(128, 128, 128)', #grey 
              'rgb(0, 255, 0)', #green 
              'rgb(192, 192, 192)'] #light grey

dcolorsc = discrete_colorscale(bvals, colors_map)
bvals = np.array(bvals)
tickvals = [np.mean(bvals[k:k+2]) for k in range(len(bvals)-1)]
ticktext  = ['param 1', 
             'param 2',
             'param 3',
             'param 4',
             'param 5',
             'param 6',
             'param 7']

fig_df = ff.create_annotated_heatmap(final_df.values.tolist(), 
                                      x= list(final_df.columns), 
                                      y=list(final_df.index), 
                                      annotation_text  = cell_labels, 
                                      colorscale=dcolorsc,
                                      colorbar = dict(thickness=25, 
                                                      tickvals=tickvals, 
                                                      ticktext=ticktext),
                                      showscale  = True,
                                      zmin=0, zmax=7,
                                      ygap = 1,
                                      xgap = 1,
                                      )
fig_df.update_layout(
    xaxis={'title' : 'ID 1'},
    yaxis = {'title' : 'ID 2'},
    yaxis_nticks = len(final_df.index),
    xaxis_nticks = len(final_df.columns)
    )

fig_df.write_html(results_file_df)

Solution

I suspect that the annotations are very expensive for plotly to render. It may be that even if you are passing a 212×64 array of empty strings to the annotation_text argument, plotly still has to go through them all to determine that there are no annotations to add.

I created a 212×64 array with random integers from 0-6 and it was also very slow to render in my browser and I got the same "page not responding" warnings as you.

When I used go.heatmap, I was able to obtain what appears to be the same plot as ff.create_annotated_heatmap, and this improved the execution time from 5-6 seconds down to 0.66 seconds, and it also responds much faster in the browser.

This seems more straightforward than creating an annotated heatmap and not using the annotations (is there a particular reason you need ff.create_annotated_heatmap instead of go.heatmap?)

import numpy as np
import pandas as pd
import plotly.figure_factory as ff
import plotly.graph_objects as go

import time
start_time = time.time()

def discrete_colorscale(bvals, colors):
    #https://chart-studio.plotly.com/~empet/15229/heatmap-with-a-discrete-colorscale/#/
    """
    bvals - list of values bounding intervals/ranges of interest
    colors - list of rgb or hex colorcodes for values in [bvals[k], bvals[k+1]],0<=k < len(bvals)-1
    returns the plotly  discrete colorscale
    """
    if len(bvals) != len(colors)+1:
        raise ValueError('len(boundary values) should be equal to  len(colors)+1')
    bvals = sorted(bvals)     
    nvals = [(v-bvals[0])/(bvals[-1]-bvals[0]) for v in bvals]  #normalized values
    
    dcolorscale = [] #discrete colorscale
    for k in range(len(colors)):
        dcolorscale.extend([[nvals[k], colors[k]], [nvals[k+1], colors[k]]])
    return dcolorscale


#final_df is a 212 row x 64 col df of ints ranging from 0 to 6
#cell_df is an empty 212x64 df of empty strings to remove cell labelling behaviour

## recreate your dfs

np.random.seed(42)
final_df = pd.DataFrame(np.random.randint(0,6,size=(212, 64)), columns=list(range(64)))

# cell_df = final_df.applymap(lambda x: annot_map.get(x, x)) 
cell_df = pd.DataFrame(np.array(['']*212*64).reshape(212,64), columns=list(range(64)))
cell_labels = cell_df.values.tolist()
bvals = [0,1,2,3,4,5,6,7]

colors_map = ['rgb(244,244,255)', #whiteish 
              'rgb(255, 128, 0)', #orange 
              'rgb(255,0,0)', #red 
              'rgb(0, 0, 255)', #blue 
              'rgb(128, 128, 128)', #grey 
              'rgb(0, 255, 0)', #green 
              'rgb(192, 192, 192)'] #light grey

dcolorsc = discrete_colorscale(bvals, colors_map)
bvals = np.array(bvals)
tickvals = [np.mean(bvals[k:k+2]) for k in range(len(bvals)-1)]
ticktext  = ['param 1', 
             'param 2',
             'param 3',
             'param 4',
             'param 5',
             'param 6',
             'param 7']

# fig_df = ff.create_annotated_heatmap(final_df.values.tolist(), 
#                                       x= list(final_df.columns), 
#                                       y=list(final_df.index), 
#                                       annotation_text  = cell_labels, 
#                                       colorscale=dcolorsc,
#                                       colorbar = dict(thickness=25, 
#                                                       tickvals=tickvals, 
#                                                       ticktext=ticktext),
#                                       showscale  = True,
#                                       zmin=0, zmax=7,
#                                       ygap = 1,
#                                       xgap = 1,
#                                       )

fig_df = go.Figure([go.Heatmap(
    z=final_df,
    colorscale=dcolorsc,
    colorbar=dict(
        thickness=25, 
        tickvals=tickvals, 
        ticktext=ticktext),
    showscale=True,
    zmin=0, zmax=7,
    ygap=1,
    xgap=1,
    )
])

fig_df.update_layout(
    xaxis={'title' : 'ID 1'},
    yaxis = {'title' : 'ID 2'},
    yaxis_nticks = len(final_df.index),
    xaxis_nticks = len(final_df.columns)
    )

fig_df.show()

print(f"Program executed in {time.time() - start_time} seconds")

## original code with figure_factory annotated heatmap: Program executed in 5.351915121078491 seconds
## modified code with graph_objects heatmap: Program executed in 0.6627509593963623 seconds
# fig_df.write_html(results_file_df)

enter image description here

Answered By – Derek O

Answer Checked By – Jay B. (BugsFixing Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *