0

Code was working with one callback function, but when i added a second callback(change title color feature) everything stopped working and i was given a blank canvas. I would like to be able to see the bar chart, with live adjutable size graph(first callback) and manually adjusting title color(second title) PLEASE HELP, THANK YOU IN ADVANCE! :>

With this dataframe

from dash import Dash, dcc, html, Input, Output
import plotly.express as px
from jupyter_dash import JupyterDash
import dash_daq as daq

app = JupyterDash(__name__)

colors = {
    'background': '#111111',
    'text': '#7FDBFF'
}

picker_style = {'float': 'left', 'margin': 'auto'}

app.layout = html.Div([
    html.H1(
        children='Hello Dash',
        style={
            'textAlign': 'center',
            'color': colors['text']
        }
    ),
    html.P('Live adjustable graph-size(using Dash python)', style={'textAlign': 'center','color': colors['text']}),
    html.P('Please manually Change figure width of your choice(+-200px):', style={'textAlign': 'center','color': colors['text']}),
    # html.P("Change figure width:", style={'color': colors['text']}),
    # dcc.Slider(id='slider', min=500, max=1900, step=200, value=1700,
    #            marks={x: str(x) for x in [500, 700,900,1100,1300,1500,1700, 1900]},updatemode='drag',tooltip={"placement": "bottom", "always_visible": True}),
    dcc.Slider(id='slider', min=500, max=1200, step=100, value=1100,
               marks={x: str(x) for x in [500,600,700,800,900,1000,1100,1200]},updatemode='drag',tooltip={"placement": "bottom", "always_visible": True}),
    dcc.Graph(id="graph"),
    daq.ColorPicker(
        id='font', label='Font Color', size=150,
        style=picker_style, value=dict(hex='#119DFF')),
    daq.ColorPicker(
        id='title', label='Title Color', size=150,
        style=picker_style, value=dict(hex='#F71016')),
])


@app.callback(
    Output("graph", "figure"), 
    Input('slider', 'value'))
    
# def display_value(width):
#     return 'Width Value: {}'.format(width)

def resize_figure(width):
    fig = go.Figure(data=[
        go.Bar(name='Monthly Concession Passes',
               x=concession["cardholders"],
               y=concession["train_price"],
               text = concession["train_price"]),
        go.Bar(name='Average Fees using Card',
               x=concession["cardholders"],
               y = concession["MRT Fees"],
               text = round(concession["Average Card Fees"],1)),
        go.Bar(name='Single Trip Fees(cash)',
               x=concession["cardholders"],
               y=concession["Single Trip Fees(cash)"],
               text = round(concession["Single Trip Fees(cash)"],1))
    ])
    fig.update_layout(
        barmode='group',
        template="plotly_dark",
        paper_bgcolor=colors['background'],
        font_color=colors['text'],
        title_text=
        'Average Monthly Expenditure comparing Concession Passes, Card and Cash for buses',
        title_x=0.5,
        yaxis={
            'title': 'Fees ($)',
            # 'rangemode': 'tozero',
            'ticks': 'outside'
        })
    fig.update_layout(width=int(width))

    return fig

# @app.callback(
    # Output("graph", 'figure'), 
    # Input("font", 'value'),
    # Input("title", 'value')
    # )
# def update_bar_chart(font_color, title_color):
#     fig = go.Figure(data=[
#         go.Bar(name='Monthly Concession Passes',
#                x=concession["cardholders"],
#                y=concession["train_price"],
#                text = concession["train_price"]),
#         go.Bar(name='Average Fees using Card',
#                x=concession["cardholders"],
#                y = concession["MRT Fees"],
#                text = round(concession["Average Card Fees"],1)),
#         go.Bar(name='Single Trip Fees(cash)',
#                x=concession["cardholders"],
#                y=concession["Single Trip Fees(cash)"],
#                text = round(concession["Single Trip Fees(cash)"],1))
#     ])
#     fig.update_layout(
#         font_color=font_color['hex'],
#         title_font_color=title_color['hex'])
#     return fig


#app.run_server(mode="inline")

this is data in dictionary:

{'cardholders': {0: 'Primary Student',
  1: 'Secondary Student',
  2: 'Polytechnic Student',
  3: 'University Student',
  4: 'Full-time National Serviceman',
  5: 'Senior Citizen',
  6: 'Adult (Monthly Travel Pass)'},
 'bus_price': {0: 24.0, 1: 29.0, 2: 29.0, 3: 55.5, 4: 55.5, 5: 64.0, 6: 128.0},
 'train_price': {0: 21.0,
  1: 26.5,
  2: 26.5,
  3: 48.0,
  4: 48.0,
  5: 64.0,
  6: 128.0},
 'hybrid_price': {0: 43.5,
  1: 54.0,
  2: 54.0,
  3: 90.5,
  4: 90.5,
  5: 64.0,
  6: 128.0},
 'Average Card Fees': {0: 8.149223099487395,
  1: 8.149223099487395,
  2: 8.149223099487395,
  3: 8.149223099487395,
  4: 20.208239081660064,
  5: 11.538449007368001,
  6: 20.208239081660064},
 'Average Cash Fees': {0: 17.756768358801253,
  1: 17.756768358801253,
  2: 17.756768358801253,
  3: 17.756768358801253,
  4: 30.431152919426268,
  5: 22.514797400960248,
  6: 30.431152919426268},
 'Single Trip Fees(cash)': {0: 69.0,
  1: 69.0,
  2: 69.0,
  3: 69.0,
  4: 69.0,
  5: 69.0,
  6: 69.0},
 'MRT Fees': {0: 12.0, 1: 12.0, 2: 12.0, 3: 12.0, 4: 40.5, 5: 20.7, 6: 40.5}}
Derek O
  • 16,770
  • 4
  • 24
  • 43
mobly elliot
  • 65
  • 1
  • 4
  • welcome to stackoverflow! it would be much easier for us to help you if we can reproduce your dash app without recreating your dataframe from scratch. can you include your sample dataframe as formatted text instead of an image? you can copy and paste the output from `concession.head(10).to_dict()` directly into your question? also just an observation – it doesn't appear that either of your callbacks modifies the data in your `fig`. it seems like it would make more sense to define your figure outside of the callback, and only use the callbacks to modify the layout – Derek O Jan 28 '23 at 04:00
  • ok i have converted it to a dictionary, but im still unsure how to make it work – mobly elliot Jan 28 '23 at 05:00
  • thanks! i have a few ideas and i'll post an answer later :) – Derek O Jan 28 '23 at 05:03

1 Answers1

0

The main issue is that you cannot have duplicate callback outputs – this isn't supported in Dash (probably because it could lead to a race condition if you have multiple callbacks trying to modify the same figure and they execute at the same time). I understand why you want to break up your callbacks, but in this case, we can actually condense down your code quite a bit anyway, and make your callback more focused.

First, I would define the figure outside of the callback and use dcc.Graph(id="graph", figure=fig) so that the figure is present when the app starts running – this way we don't need to redefine the figure in the callback every time it's executed – we'll design the callback so it only modifies only the width, font color, and title color of the figure's layout.

Then we can combine your two callbacks into one by collecting all of the inputs from both of your original callbacks, and also pass the figure as an input.

Note that when a figure is passed to the callback as an input, it will be in the form of a dictionary, so we'll want to convert the fig argument from the callback to a go.Figure object using something like plotly_fig = go.Figure(fig_dict). Then you can modify the layout of plotly_fig based on the other inputs using plotly_fig.update_layout(...).

Here is a working example:

import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
from dash import Dash, dcc, html, Input, Output
from jupyter_dash import JupyterDash
import dash_daq as daq

concession = pd.DataFrame({'cardholders': {0: 'Primary Student',
  1: 'Secondary Student',
  2: 'Polytechnic Student',
  3: 'University Student',
  4: 'Full-time National Serviceman',
  5: 'Senior Citizen',
  6: 'Adult (Monthly Travel Pass)'},
 'bus_price': {0: 24.0, 1: 29.0, 2: 29.0, 3: 55.5, 4: 55.5, 5: 64.0, 6: 128.0},
 'train_price': {0: 21.0,
  1: 26.5,
  2: 26.5,
  3: 48.0,
  4: 48.0,
  5: 64.0,
  6: 128.0},
 'hybrid_price': {0: 43.5,
  1: 54.0,
  2: 54.0,
  3: 90.5,
  4: 90.5,
  5: 64.0,
  6: 128.0},
 'Average Card Fees': {0: 8.149223099487395,
  1: 8.149223099487395,
  2: 8.149223099487395,
  3: 8.149223099487395,
  4: 20.208239081660064,
  5: 11.538449007368001,
  6: 20.208239081660064},
 'Average Cash Fees': {0: 17.756768358801253,
  1: 17.756768358801253,
  2: 17.756768358801253,
  3: 17.756768358801253,
  4: 30.431152919426268,
  5: 22.514797400960248,
  6: 30.431152919426268},
 'Single Trip Fees(cash)': {0: 69.0,
  1: 69.0,
  2: 69.0,
  3: 69.0,
  4: 69.0,
  5: 69.0,
  6: 69.0},
 'MRT Fees': {0: 12.0, 1: 12.0, 2: 12.0, 3: 12.0, 4: 40.5, 5: 20.7, 6: 40.5}})

app = JupyterDash(__name__)

colors = {
    'background': '#111111',
    'text': '#7FDBFF'
}

picker_style = {'float': 'left', 'margin': 'auto'}

## define the figure 
fig = go.Figure(data=[
    go.Bar(name='Monthly Concession Passes',
           x=concession["cardholders"],
           y=concession["train_price"],
           text = concession["train_price"]),
    go.Bar(name='Average Fees using Card',
           x=concession["cardholders"],
           y = concession["MRT Fees"],
           text = round(concession["Average Card Fees"],1)),
    go.Bar(name='Single Trip Fees(cash)',
           x=concession["cardholders"],
           y=concession["Single Trip Fees(cash)"],
           text = round(concession["Single Trip Fees(cash)"],1))
])
fig.update_layout(
        barmode='group',
        template="plotly_dark",
        paper_bgcolor=colors['background'],
        font={
            'color': colors['text']
        },
        title_text=
        'Average Monthly Expenditure comparing Concession Passes, Card and Cash for buses',
        width=1100,
        title_x=0.5,
        title={
            'text': 'Fees ($)',
            'font': {'color': '#F71016'}
        },
        yaxis={
            # 'title': 'Fees ($)',
            # 'rangemode': 'tozero',
            'ticks': 'outside'
        })

app.layout = html.Div([
    html.H1(
        children='Hello Dash',
        style={
            'textAlign': 'center',
            'color': colors['text']
        }
    ),
    html.P('Live adjustable graph-size(using Dash python)', style={'textAlign': 'center','color': colors['text']}),
    html.P('Please manually Change figure width of your choice(+-200px):', style={'textAlign': 'center','color': colors['text']}),
    # html.P("Change figure width:", style={'color': colors['text']}),
    # dcc.Slider(id='slider', min=500, max=1900, step=200, value=1700,
    #            marks={x: str(x) for x in [500, 700,900,1100,1300,1500,1700, 1900]},updatemode='drag',tooltip={"placement": "bottom", "always_visible": True}),
    dcc.Slider(id='slider', min=500, max=1200, step=100, value=1100,
               marks={x: str(x) for x in [500,600,700,800,900,1000,1100,1200]},updatemode='drag',tooltip={"placement": "bottom", "always_visible": True}),
    dcc.Graph(id="graph", figure=fig),
    daq.ColorPicker(
        id='font', label='Font Color', size=150,
        style=picker_style, value=dict(hex='#119DFF')),
    daq.ColorPicker(
        id='title', label='Title Color', size=150,
        style=picker_style, value=dict(hex='#F71016')),
])

@app.callback(
    Output("graph", "figure"), 
    [Input("graph", "figure"),
    Input('slider', 'value'),
    Input("font", 'value'),
    Input("title", 'value')],
    prevent_initial_call=True
)
def update_figure(fig_dict, width, font_color, title_color):
    plotly_fig = go.Figure(fig_dict)
    plotly_fig.update_layout(
        width=int(width),
        font_color=font_color['hex'],
        title_font_color=title_color['hex']
    )
    return plotly_fig

## I ran this externally, but you can run this inline
app.run_server(mode="external", port=8050)

enter image description here

Derek O
  • 16,770
  • 4
  • 24
  • 43