✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
🧬 Learn how to build RNA-Seq data apps with Python & Dash. Register for the May 20 Webinar!

Sorting visualizer, how to use callbacks to implement this function?

I am trying to create a sorting visualizer.
I am able to implement the live changing graph in jupyter notebook, but I am failing to understand where exactly I should call my function.
Or how can the dcc.Interval help me in this regard
Here is the code:

import dash
import dash_core_components as dcc
import dash_html_components as html
from datetime import datetime, timedelta
import dash_table
from dash.dependencies import Input, Output
import plotly_express as px
import random
import plotly.graph_objects as go
import time

app = dash.Dash(__name__)
app.title = "Sorting Visualizer"
server = app.server

colors = {
    'background': '#111111',
    'text': '#BEBEBE',
    'grid': '#333333'
}



the_colors = ['lightslategray', ]*10
the_nums = [random.randint(1, 100) for i in range(0, 10)]

fig = go.Figure()
fig.add_trace(go.Bar(
    y = the_nums,
    marker_color = the_colors)
             )

fig.update_layout(
    xaxis={'title': 'NUMBERS ','fixedrange':True},
    font=dict(color=colors['text']),
    paper_bgcolor=colors['background'],
    plot_bgcolor=colors['background'],
    height = 600,
    width = 1200,
    hovermode='closest',
    showlegend=True,
)

app.layout = html.Div(
        id = "sorting_layout",
        children = [
        dcc.Graph(id = 'new_sorter', figure = fig),
            dcc.Interval(
            id='interval-component',
            interval=1*1000, # in milliseconds
            n_intervals=0)
        ])


#sort1(fig.data[0],fig.layout,10, the_colors)
if __name__ == '__main__':
    app.run_server(debug=False)
    sort1(fig.data[0],fig.layout,10, the_colors)

And the code for basic bubble sort which works by showing changes in colors is:

def sort1(bar, layout, num, colors):
    data = list(bar['y'])
    count = 0
    for i in range(num):
        for j in range(num-i-1):

            if data[j] > data[j+1]:
                colors[j] = 'blue'
                colors[j+1] = 'blue'
                fig.update

                time.sleep(0.1)
                bar['marker']['color'] = colors
                data[j], data[j+1] = data[j+1], data[j] # interchange the value
                bar['y'] = data # resend the new data list, after updations
                time.sleep(0.1)

            else:
                colors[j] = 'green'
                colors[j+1] = 'green'
                time.sleep(0.1)
                bar['marker']['color'] = colors # keep sending the updated colors list whe changed
                time.sleep(0.1)
            
            # current operations are done go back to default color
            colors[j] = 'lightslategray'
            colors[j+1] = 'lightslategray'
            bar['marker']['color'] = colors
            count = count + 1
            layout.xaxis.title.text = "Number of operations: " + str(count)

        colors[num-i-1] = 'red' # done with this guy
        bar['marker']['color'] = colors # udpated it back
    
    return fig

I am just getting started with Dash and plotly, so pardon me for any begineer mistakes

Hi @akshit0699 and welcome to the Dash forum!

That’s a fun one - it will be a cool visualization!

It looks like you have both the start figure and the sort function working - right?

I’m thinking the easiest place to start is to create a list of figures by appending a figure at each step in the sort function. Then in a callback you can “replay” each step in the list with dcc.Interval at various speeds, or step by step with a button click… Or start over with new figure.

This may not be the most efficient solution since at start-up it builds a list of all of the figures in each step. But since this isn’t a huge dataset, it may not be a problem.

I’m interested to see what solution you come up with. If you need more detailed examples of what I suggested, let me know.

1 Like

Thanks a lot, your idea seems to be pretty nice, I would surely like to know more if you may help.
What I was thinking is trying to use fig.update_traces() inside my sort function, that whenever I make changes in the_colors list or the_nums list, I can somehow reflect that onto my graph.
Now I wasn’t able to understand from the documentation as to whether or not I can make a slective update to my graph using this feature,
Also this would not show the changes on the graph in real time, can you please help how to couple up this thinking with dcc.Interval? Or if you can help further with the method that you suggested. It would be a great help!!
Here I am also uploading what I have been able to do in my jupyter notebook

SortingViz|640x292

I’m happy to help - this is a great project.

I like how you tested the individual pieces. I did that too with the code you provided, so I know it works - good job :slight_smile:

To continue to build your app step-by-step, I’d like you to try to change your sort1 function so it returns a list of figures at each step.

As you mentioned in your last post, it’s not easy to just update certain parts of a figure in a callback (maybe this will change in the future). For now, your callback will return the whole figure with the changes at each step. (Since you don’t have large amounts of data, this will not be a significant performance issue)

The next phase will be to add the callbacks. This may be tomorrow - it’s getting late here :slight_smile:

1 Like

That will be amazing, great to have people like you here, I will try to do what I can untill then, looking forward for more! :slight_smile:

Alright so the problem has been solved using the plotly animation feature.
We can create a list of all the frames coming during the function and then run then back to back using the animation feature.
Read here: https://plotly.com/python/animations/#using-a-slider-and-buttons

def sort1(bar, layout, num, colors):
    data = list(bar["y"])
    count = 0
    frames = []
    for i in range(num):
        for j in range(num - i - 1):
            if data[j] > data[j + 1]:
                colors[j] = "blue"
                colors[j + 1] = "blue"
                bar["marker"]["color"] = colors
                data[j], data[j + 1] = data[j + 1], data[j]  # interchange the value
                bar["y"] = data  # resend the new data list, after updations
                frames.append(go.Frame(data=[bar]))
            else:
                colors[j] = "green"
                colors[j + 1] = "green"
                bar["marker"][
                    "color"
                ] = colors  # keep sending the updated colors list whe changed
                frames.append(go.Frame(data=[bar]))

            # current operations are done go back to default color
            colors[j] = "lightslategray"
            colors[j + 1] = "lightslategray"
            bar["marker"]["color"] = colors
            count = count + 1
            layout.xaxis.title.text = "Number of operations: " + str(count)
            frames.append(go.Frame(data=[bar]))

        colors[num - i - 1] = "red"  # done with this guy
        bar["marker"]["color"] = colors  # udpated it back
        frames.append(go.Frame(data=[bar]))
    return frames


fig.frames = sort1(fig.data[0], fig.layout, 10, the_colors)
fig.update_traces(y=start_nums, marker_color=start_colors)

And the layout now goes like:

fig.update_layout(
    xaxis={"title": "NUMBERS ", "fixedrange": True},
    font=dict(color=colors["text"]),
    paper_bgcolor=colors["background"],
    plot_bgcolor=colors["background"],
    height=600,
    width=1200,
    hovermode="closest",
    showlegend=True,
    updatemenus=[
        dict(
            type="buttons", buttons=[dict(label="Play", method="animate", args=[None])]
        )
    ],
)

Thanks a lot @AnnMarieW for helping along all this and colaborating.
This method however would take up its time to build up the list, and would be slow for large input size of the array. A better approach would be an algorithm with real time update, which I will be putting up a link to, shortly :slight_smile:

1 Like