Black Lives Matter. Please consider donating to Black Girls Code today.

How to update a html content after a calculation function is done?

I want to update a html content after a calculation from the server side to the client side.

I have an example where a random image is created in background but to see the image/content immediately I need to wait (from the client side) an amount of time before I get the content from the server. (now imagine the calculation would need/take > 10 seconds or even minutes!)

Here is the example code:

import base64
import dash
import flask
import hashlib
import inspect
import os

import dash_core_components as dcc
import dash_html_components as html
import numpy as np

from PIL import Image

from dash.dependencies import Input, Output


STATIC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static')

if not os.path.exists(STATIC_PATH):
    os.makedirs(STATIC_PATH)

if STATIC_PATH[-1] != "/":
    STATIC_PATH += "/"


external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

server = flask.Flask(__name__)
app = dash.Dash(__name__, server=server, external_stylesheets=external_stylesheets)
app.config['suppress_callback_exceptions']=True

app.title = 'Random Images'

colors = {
    'fg_div1': '#7FFF80',

    'fg_h1': '#6734D2',
    'fg_p': '#E0F040',
    
    'bg_button_1': '#30E090',
    'bg_button_2': '#40A0A0',
}

def get_random_picture(height, width):
    return Image.fromarray(np.random.randint(0, 256, (height, width, 3), dtype=np.uint8))


def get_sha512_value(file_path):
    with open(file_path, 'rb') as fin:
        hexdigest = hashlib.sha512(fin.read()).hexdigest()
    return hexdigest


def create_random_image(size):
    img = get_random_picture(size, size)
    img.save(STATIC_PATH+'random_image_{}_{}.png'.format(size, size))


app.layout = html.Div(
className='row',
style={
    'margin-left': 'auto',
    'margin-right': 'auto',
    'display': 'flex',
    'justify-content': 'center',
    'align-items': 'center',
},
children=[
    html.Div(
    style={
        'margin-left': 'auto',
        'margin-right': 'auto',
        'width': '50%',
        'margin': '0%',
    },
    children=[
        html.Div(
        style={
            'textAlign': 'center',
        },
        children=[
            html.H1(
            style={
                'textAlign': 'center',
                'color': colors['fg_h1'],
                'display': 'inline-block',
                'margin-top': '20px',
            },
            children=[
                'Random Image'
            ]),
        ]),

        html.Div(
        style={'textAlign': 'center'},
        children=[
            html.P(
            style={
                'textAlign': 'center',
                'color': colors['fg_p'],
                'display': 'inline-block',
                'font-size': '16px',
            },
            children=[
                'Create a simple random image!'
            ]),
        ]),

        html.Div(
        className='row',
        style={
            'margin-left': 'auto',
            'margin-right': 'auto',
        },
        children=[
            html.Div(
            className='row',
            style={
                'text-align': 'center',
            },
            children=[
                html.Span(
                id='create_128_128_image',
                className='button button--primary add noselect',
                style={
                    'background-color': colors['bg_button_1'],
                    'margin-left': '10px',
                    'margin-right': '10px',
                },
                children=[
                    'Create 128x128 Random Image'
                ]),

                html.Span(
                id='load_128_128_image',
                n_clicks=0,
                className='button button--primary add noselect',
                style={
                    'background-color': colors['bg_button_2'],
                    'margin-left': '10px',
                    'margin-right': '10px',
                },
                children=[
                    'Load 128x128 Image'
                ]),

                html.Span(
                id='remove_128_128_image',
                n_clicks=0,
                className='button button--primary add noselect',
                style={
                    'background-color': colors['bg_button_2'],
                    'margin-left': '10px',
                    'margin-right': '10px',
                },
                children=[
                    'Remove 128x128 Image'
                ]),
            ]),

            html.Div(
            id='random_image_display',
            ),
        ]),
    ]),
])


# size is for height and width of the image
def button_pressed_random_picture(size, more_text=''):
    file_name = 'random_image_{}_{}.png'.format(size, size)
    file_path = STATIC_PATH+'/'+file_name

    sha512_value = get_sha512_value(file_path).upper()

    sha512_value_split = (lambda length: [sha512_value[length*i:length*(i+1)] for i in range(0, 4)])(len(sha512_value)//4)

    with open(file_path, 'rb') as fin:
        encoded_image = base64.b64encode(fin.read())
    src='data:image/png;base64,{}'.format(encoded_image.decode())
    
    print('Now it is {}x{} a random image'.format(size, size))

    return [
        html.Div(
        style={
            'color': colors['fg_div1'],
            'text-align': 'center',
            'margin-top': '10px',
            'margin-bottom': '10px',
        },
        children=[
            html.Div('Test {}x{}{}'.format(size, size, more_text)),
            
            html.Div('SHA512 sum of file "{}":'.format('/static/'+file_name)),
            ]+[
                html.Div('{}'.format(sha512_value_part),
                style={
                    'font-family': 'monospace, sans-serif',
                    'margin-left': 'auto',
                    'margin-right': 'auto',
                })
                for sha512_value_part in sha512_value_split
            ]+[
            html.Img(
            id='random_image',
            src=src,
            style={
                'height': '{}px'.format(size),
                'width': '{}px'.format(size),
            }),
        ]),
    ]


@app.callback(
    Output('random_image_display', 'children'),
    [
        Input('create_128_128_image', 'n_clicks_timestamp'),
        Input('load_128_128_image', 'n_clicks_timestamp'),
        Input('remove_128_128_image', 'n_clicks_timestamp'),
    ]
)
def display_calculation_labyrinth(nt1, nt2, nt3):
    if nt1 == None:
        nt1 = 0
    if nt2 == None:
        nt2 = 0
    if nt3 == None:
        nt3 = 0

    button_num = np.argmax([nt1, nt2, nt3])
    print('button_num: {}'.format(button_num))

    if button_num == 0:
        # TODO: First update random_image_display with e.g. html.Div('Calculating new image...')
        create_random_image(128)
        # TODO: After the calculation/function do an update of the content in random_image_display!
        layout = button_pressed_random_picture(128)
    elif button_num == 1:
        size = 128
        layout = button_pressed_random_picture(128, more_text=', only loaded the image now!')
    elif button_num == 2:
        layout = [
            html.Div('Delted Content!',
            style={
                'color': '#FFFFFF',
                'text-align': 'center'
            })
        ]
    
    return layout


if __name__ == '__main__':
    app.run_server(debug=True, host='0.0.0.0', port='31415')

Consider after pressing the create button, there should normally come a message like ‘Image is in process, waiting…’ or what ever.

Now I have seen some questions like here (which is empty so far),
Or here, but I know I could use dcc.Interval to make this working.

To be more precise: I only want to update the content when the calculation/function is finished!

Is there already a similar problem with a solution? Or is there maybe another approach to solve this kind of problem?

Any kind of help would be really nice and thank you in advance.

Here’s one example of how to show a “loading…” message while the calculation is in-progress: In a plotly dash app, how to show a default text value in a div upon each click (on a dropdown, or button, etc) until the div is populated?

1 Like

Thank you, I would never thought about the idea like with this example what is in the link :see_no_evil:.