Dash Wordcloud interactive with different lists to generate from

Hello!

I am trying to use a word cloud in my Dashboard. I have multiple topics from which the cloud can be generated and I have a dropdown that selects the topic. But I seem to do something wrong because the word cloud is not shown in the dashboard.

This is my code:

import dash
from dash import html
import dash_core_components as dcc
import plotly.express as px
import numpy as np
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
from dash import Dash, dash_table
from wordcloud import WordCloud
import matplotlib.pyplot as plt
import base64
from io import BytesIO

app = dash.Dash(__name__)

app.layout = html.Div( children=[
html.Div([
    dcc.Dropdown(
        id='topicwc_drop',
        options=[
            {'label': f'Topic {i}', 'value': i} for i in topics.keys()],
        value=0),
        html.Img(id="image_wc")])
])

@app.callback(
    Output('image_wc', 'src'),
    Input(topicwc_drop, 'value'))

def plot_wordcloud(topicwc_v):
    text = {word: value for word, value in model_bert.get_topic(topicwc_v)}
    wc = WordCloud(background_color="white", max_words=1000)
    wc.generate_from_frequencies(text)
    wc.to_image()
    
    img = BytesIO()
    wc.save(img, format='PNG')
    return 'data:image/png;base64,{}'.format(base64.b64encode(img.getvalue()).decode())

if __name__ == "__main__":
    app.run_server(host="localhost",port=8003)

‘topics’ is a dictionary and the topic range is -1 to 94. The model_bert generates the topics.

Hi @elmo welcome to the forums.

Usually it’s a good idea to run the app in debug mode if the app does not work as expected.

app.run_server(host="localhost", port=8003, debug=True)

You forgot to wrap the component id in ’ ’

Hey AIMPED,

Thanks for your answer! I am working in Jupiter Lab in Safari. When I load the dashboard with debug=True, it fails.

You probably mean adding

Input('image_wc', 'id')

to the callback.

I actually had that in there before. Tried adding it again, doesn’t show the wordcloud either. But I’ll keep it in the code and will keep trying.

No :slight_smile:

Instead of

this:

@app.callback(
    Output('image_wc', 'src'),
    Input('topicwc_drop', 'value'))

Concerning the debugging: try adding this:

app.run_server(host="localhost", port=8003, debug=True, use_reloader=False)
1 Like

Hey AIMED,

Sorry just realized that I missunderstood you. That doesn’t change anything either unfortunately…

Adding debug=True, use_reloader=False shows a callback error, but also doesn’t fix the problem.

Thanks for helping!

OK, you see a callback error. That is because the debug mode is active and working.

What is the callback error message?

Oh that might help! Didn’t realize it gives more info.

It’s TypeError: plot_wordcloud() takes 1 positional argument but 5 were given.

I guess that is because I have more Inputs in my callback? Do you know how to handle multiple functions for the dashboard?

I reduced my code to the word cloud-related part before. This is the full:

@app.callback(
    Output(docs_info_table, 'data'),
    Output(docs_info_table, 'page_size'),
    Output('image_wc', 'src'),
    #Input('image_wc', 'id'),
    [Input('topicwc_drop', 'value')],
    [Input(year_drop, 'value'),
    Input(journal_drop, 'value'),
    Input(topic_drop, 'value'),
    Input(row_drop, 'value')],
)

def plot_wordcloud(topicwc_v):
    text = {word: value for word, value in model_bert.get_topic(topicwc_v)}
    wc = WordCloud(background_color="white", max_words=1000)
    wc.generate_from_frequencies(text)
    wc.to_image()
    
    img = BytesIO()
    wc.save(img, format='PNG')
    return 'data:image/png;base64,{}'.format(base64.b64encode(img.getvalue()).decode())


def update_dropdown_options(year_v, journal_v, topic_v, row_v):
    dff = docs_info.copy()

    if year_v:
        dff = dff[dff.Year.isin(year_v)]
        
    if journal_v:
        dff = dff[dff.Journal.isin(journal_v)]
        
    if topic_v:
        dff = dff[dff.Topic.isin(topic_v)]

    return dff.to_dict('records'), row_v

I had the idea to put it all into one function:

def update_dropdown_options(year_v, journal_v, topic_v, row_v, topicwc_v):
    dff = docs_info.copy()

    if year_v:
        dff = dff[dff.Year.isin(year_v)]
        
    if journal_v:
        dff = dff[dff.Journal.isin(journal_v)]
        
    if topic_v:
        dff = dff[dff.Topic.isin(topic_v)]
    
    text = {word: value for word, value in model_bert.get_topic(topicwc_v)}
    wc = WordCloud(background_color="white", max_words=1000)
    wc.generate_from_frequencies(text)
    wc.to_image()
    
    img = BytesIO()
    wc.save(img, format='PNG')
    return 'data:image/png;base64,{}'.format(base64.b64encode(img.getvalue()).decode())

    return dff.to_dict('records'), row_v, 'data:image/png;base64,{}'.format(base64.b64encode(img.getvalue()).decode())

I think it works, because I am having a new callback error:

Traceback (most recent call last):
  File "/var/folders/kj/dkjqkk2n3wq2zfbttgdpjrj80000gn/T/ipykernel_5478/68932548.py", line 272, in update_dropdown_options
    wc.save(img, format='PNG')
AttributeError: 'WordCloud' object has no attribute 'save'

The error message is pretty explicit…

You could also split this into two callbacks which IHMO makes more sense:

@app.callback(
    Output('image_wc', 'src'),
    Input('topicwc_drop', 'value')
)
def plot_wordcloud(topicwc_v):
    text = {word: value for word, value in model_bert.get_topic(topicwc_v)}
    wc = WordCloud(background_color="white", max_words=1000)
    wc.generate_from_frequencies(text)
    wc.to_image()

    img = BytesIO()
    wc.save(img, format='PNG')
    return 'data:image/png;base64,{}'.format(base64.b64encode(img.getvalue()).decode())


@app.callback(
    Output(docs_info_table, 'data'),
    Output(docs_info_table, 'page_size'),
    [
        Input(year_drop, 'value'),
        Input(journal_drop, 'value'),
        Input(topic_drop, 'value'),
        Input(row_drop, 'value')
    ],
)
def update_dropdown_options(year_v, journal_v, topic_v, row_v):
    dff = docs_info.copy()

    if year_v:
        dff = dff[dff.Year.isin(year_v)]

    if journal_v:
        dff = dff[dff.Journal.isin(journal_v)]

    if topic_v:
        dff = dff[dff.Topic.isin(topic_v)]

    return dff.to_dict('records'), row_v
1 Like

I don’t have your dataframe so please refer below code to revise yours:

import pandas as pd
import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
from io import BytesIO
import base64
import dash.dependencies as dd
from wordcloud import WordCloud

df = pd.read_csv('https://raw.githubusercontent.com/hoatranobita/holiday_challenge/main/indeed_jobs_data.csv')
app = dash.Dash(__name__,external_stylesheets=[dbc.themes.LUX])

app.layout = html.Div([
    dcc.Dropdown(id='dropdown',
                 options=[{'label':x, 'value':x} for x in df['Location'].unique()],
                 value='Kuala Lumpur'),
    html.Img(id='image_wc')
])

def plot_wordcloud(data):
    d = {a: x for a, x in data.values}
    wc = WordCloud(background_color='white', width=1080, height=360)
    wc.fit_words(d)
    return wc.to_image()

@app.callback(Output('image_wc', 'src'),
              [Input('image_wc', 'id'),
               Input('dropdown', 'value')])
def make_image(b,n):
    img = BytesIO()
    dff = df[df['Location'] == n]
    dff2 = dff.groupby(["Title"])["Title"].count().reset_index(name="count")
    plot_wordcloud(data=dff2).save(img, format='PNG')
    return 'data:image/png;base64,{}'.format(base64.b64encode(img.getvalue()).decode())

if __name__ == "__main__":
    app.run_server(debug=False,port=1217)

So as I understand, you should add Input('image_wc', 'id') in your callback too.

1 Like

Thanks! That would be the same as putting it ins one big function like I did :slight_smile:

Hello Hoatran,

Thanks so much! That fixed it!

As I said in my original question, I have the topics. Each topic contains 15 words and form those the word cloud is generated. The model provides probabilities for the size of the words.

This is what the code ends up as:

def plot_wordcloud(topic):
    text = {word: value for word, value in model_bert.get_topic(topic)}
    wc = WordCloud(background_color="white", max_words=1000)
    wc.generate_from_frequencies(text)
    return wc.to_image()

@app.callback(
    Output(docs_info_table, 'data'),
    Output(docs_info_table, 'page_size'),
    Input(year_drop, 'value'),
    Input(journal_drop, 'value'),
    Input(topic_drop, 'value'),
    Input(row_drop, 'value')
)

def update_dropdown_options(year_v, journal_v, topic_v, row_v):
    dff = docs_info.copy()

    if year_v:
        dff = dff[dff.Year.isin(year_v)]
        
    if journal_v:
        dff = dff[dff.Journal.isin(journal_v)]
        
    if topic_v:
        dff = dff[dff.Topic.isin(topic_v)]
        
    return dff.to_dict('records'), row_v

@app.callback(
    Output('image_wc', 'src'),
    Input('image_wc', 'id'),
    Input('topicwc_drop', 'value'))    

def make_image(b, topicwc_drop):
    img = BytesIO()
    plot_wordcloud(topicwc_drop).save(img, format='PNG')
    
    return 'data:image/png;base64,{}'.format(base64.b64encode(img.getvalue()).decode())
1 Like