Search code examples
pandasplotlyplotly-dash

plotly subplots with multiple plot types not working


I've scowled the internet and tried various ways to create a subplot with three scatterpolar plots and three bar plots.

I've come down to this...

Let's say I have three dataframes that I filtered from another database to gives me data for students and how much they completed each category. We'll use the ones below as an example of all three:

import pandas as pd 
pd.set_option('display.max_rows', None)
# Run this app with `python app.py`
from dash import Dash, dcc, html
#import plotly.express as px
from plotly.subplots import make_subplots
from plotly import graph_objects as go
import dash_bootstrap_components as dbc

import pandas as pd
Dash(assets_ignore='.*ignored.*')

app = Dash(__name__)

# neglect false warnings
pd.options.mode.chained_assignment = None  # default='warn'


# color dictionary 
class RangeDict(dict):
    def __getitem__(self, item):
        if not isinstance(item, range):
            for key in self:
                if item in key:
                    return self[key]
            raise KeyError(item)
        else:
            return super().__getitem__(item)

grade_colors = RangeDict({
                            range(0,60): 'crimson',
                            range(60,70): '#E34363',
                            range(70,80): '#FFB733',
                            range(80,90): '#29CC92',
                            range(90,101): '#339933'})

colors = {
    'background': '#111111',
    'text': 'teal',
    "0":"silver",
    "1":"#FBEC5D",
    "5":"#50C878",
    "10":"#40E0D0",
    "15":"#A23D60",
    0:"silver",
    1:"#FBEC5D",
    5:"#50C878",
    10:"#40E0D0",
    15:"#A23D60",
    'Linux':'#50C878',
    'Windows':'#66D3F4',
    'Primer':'#FFD700',
}

# pandas dataframe used for plots

students_category_earned = pd.DataFrame({
    'Category': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J','A','A','B','B','C','C'],
    'completed' : [ 55 , 55 , 100 , 95 , 45 , 99 , 75 , 64 , 93 , 10 , 15 , 55 , 45 , 78 , 98 , 33 ],
    'platform' : ['primer','primer','primer','primer','primer','primer','primer','primer', 'primer', 'primer','Linux','Windows','Linux','Windows','Linux','Windows']
    })


print( students_category_earned )
print(" - "*9)
print(students_category_earned)

students_topics_earned = pd.DataFrame({
    'topic': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J','A','A','B','B','C','C'],
    'completed' : [ 55 , 55 , 100 , 95 , 45 , 99 , 75 , 64 , 93 , 10 , 15 , 55 , 45 , 78 , 98 , 33 ],
    'platform' : ['primer','primer','primer','primer','primer','primer','primer','primer', 'primer', 'primer','Linux','Windows','Linux','Windows','Linux','Windows']
    })

print("students_topics_earned")
print("-"*9)
print(students_topics_earned)

# functions to creating the plots


def student_category_bar_grade(df,platform):
    df_filtered = df[df['platform']==platform].sort_values(by='Category',ascending=True)
    fig = go.Figure()
    for completed,sdf in df_filtered.groupby('completed'):
        color = grade_colors[completed]
        fig.add_traces(
            go.Bar(x=sdf['Category'],
                y=sdf['completed'],
                customdata=sdf['platform'],
                name=str(completed)+" %",
                marker={'color': color},
                hovertemplate="<br>".join([
                    "Platform: %{customdata}",
                    "Category: %{x}",
                    ]),
            )
        )
    # Change the bar mode
    fig.update_layout(title_text=platform)
    fig.update_layout(barmode='group',
                        plot_bgcolor=colors['background'],
                        paper_bgcolor=colors['background'],
                        font_color=colors['text']
                        )
                        
    fig.update_layout(showlegend=False) 
    fig.update_layout(
        title_x=0.5,
        title_font_size=26,
        title_font_family="Copperplate",
        title_font_color="teal",
    )
    return fig

               
def student_topic_scatter_polar_graph(df,platform):

    df_filtered = df[df['platform']==platform].sort_values(by='topic',ascending=True)
    color = colors[platform]
    
    fig = go.Scatterpolar(
                    r=df_filtered.completed,
                    theta=df_filtered.topic,
                    fill='toself',
                    name="%s - Focused Topics"%platform,
                    fillcolor=color, 
                    opacity=0.6,
                    line=dict(color=color)
    )

    return fig


# calling the plot functinos
linux_category_bar_fig = student_category_bar_grade(students_category_earned,'Linux')
windows_category_bar_fig = student_category_bar_grade(students_category_earned,'Windows')
primer_category_bar_fig = student_category_bar_grade(students_category_earned,'Primer')

linux_topic_scatter_polar_fig = student_topic_scatter_polar_graph(students_topics_earned,'Linux')
windows_topic_scatter_polar_fig = student_topic_scatter_polar_graph(students_topics_earned,'Windows')
primer_topic_scatter_polar_fig = student_topic_scatter_polar_graph(students_topics_earned,'Primer')
#create subplot
student_subplot =  make_subplots(rows=2, cols=3,
                    specs=[[{"type": "scatterpolar"}, {"type": "scatterpolar"}, {"type": "scatterpolar"}],
                        [{"type": "bar"}, {"type": "bar"}, {"type": "bar"}]]  )    
figure1_traces = []
figure2_traces = []
figure3_traces = []
# combine the figs to the subplot
for trace in range(len(linux_category_bar_fig["data"])):
    figure1_traces.append(linux_category_bar_fig["data"][trace])
for trace in range(len(windows_category_bar_fig["data"])):
    figure2_traces.append(windows_category_bar_fig["data"][trace])
for trace in range(len(primer_category_bar_fig["data"])):
    figure3_traces.append(primer_category_bar_fig["data"][trace])

student_subplot.add_trace(linux_topic_scatter_polar_fig,row=1,col=1)
student_subplot.add_trace(windows_topic_scatter_polar_fig,row=1,col=2)
student_subplot.add_trace(primer_topic_scatter_polar_fig,row=1,col=3)
student_subplot.add_trace(figure1_traces,row=1,col=1)
student_subplot.add_trace(figure1_traces,row=1,col=2)
student_subplot.add_trace(figure1_traces,row=1,col=3)

# creating card that the subplots will be in
card = dbc.Card(
                    [
                        dbc.CardBody([
                            html.H4("Student Name", className="card-title"),
                            dcc.Graph(figure=student_subplot),
                            
                        ])
                    ]
                )

app.layout = html.Div([
        card
    ],
)




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

but I keep getting an error about the 'data' property of the trace...I cannot figure it out.. could someone help me understand how to combine the two different types of plots in a 'make_subplot' ?

ALSO…the end result is supposed to look something like this…

enter image description here

sorry for the terrible depiction.


Solution

  • With respect to the data error, there are three possible causes: first, the string of the extraction condition simply does not match the string of the data frame. The data frame is 'primer' and the argument is 'Premir'. The second is that the bar chart is drawn grouped by achievement rate, which does not match the number of bars in the subplot. So it needs to be modified to get three graphs in the three platform conditions. third, I added a bar chart to fig in the function and then added more graphs outside the function. The modification is made to the graph data only. I have not addressed the decorations on the bar graph.

    fig = go.Figure()
    def student_category_bar_grade(df,platform):
        df_filtered = df[df['platform']==platform].sort_values(by='Category',ascending=True)
    
        color = [grade_colors[x] for x in df_filtered['completed']]
    
        completed = ['{} %'.format(x) for x in df_filtered['completed']]
        data = go.Bar(x=df_filtered['Category'],
                     y=df_filtered['completed'],
                     customdata=df_filtered['platform'],
                     name=platform,
                     text=completed,
                     marker={'color': color},
                     hovertemplate="<br>".join([
                         "Platform: %{customdata}",
                         "Category: %{x}",
                     ]),
                     )
    
        return data
    
                   
    def student_topic_scatter_polar_graph(df,platform):
    
        df_filtered = df[df['platform']==platform].sort_values(by='topic',ascending=True)
        color = colors[platform]
    
        fig = go.Scatterpolar(
                        r=df_filtered.completed,
                        theta=df_filtered.topic,
                        fill='toself',
                        name="%s - Focused Topics"%platform,
                        fillcolor=color, 
                        opacity=0.6,
                        line=dict(color=color),
                        mode='markers'
        )
        return fig
    
    # calling the plot functinos
    linux_category_bar_fig = student_category_bar_grade(students_category_earned,'Linux')
    windows_category_bar_fig = student_category_bar_grade(students_category_earned,'Windows')
    primer_category_bar_fig = student_category_bar_grade(students_category_earned,'primer')
    
    linux_topic_scatter_polar_fig = student_topic_scatter_polar_graph(students_topics_earned,'Linux')
    windows_topic_scatter_polar_fig = student_topic_scatter_polar_graph(students_topics_earned,'Windows')
    primer_topic_scatter_polar_fig = student_topic_scatter_polar_graph(students_topics_earned,'primer')
    #create subplot
    student_subplot =  make_subplots(rows=2, cols=3,
                        specs=[[{"type": "polar"}, {"type": "polar"}, {"type": "polar"}],
                            [{"type": "bar"}, {"type": "bar"}, {"type": "bar"}]])
    
    student_subplot.add_trace(linux_topic_scatter_polar_fig,row=1,col=1)
    student_subplot.add_trace(windows_topic_scatter_polar_fig,row=1,col=2)
    student_subplot.add_trace(primer_topic_scatter_polar_fig,row=1,col=3)
    
    student_subplot.add_trace(linux_category_bar_fig,row=2,col=1)
    student_subplot.add_trace(windows_category_bar_fig,row=2,col=2)
    student_subplot.add_trace(primer_category_bar_fig,row=2,col=3)
    
    # creating card that the subplots will be in
    card = dbc.Card(
                        [
                            dbc.CardBody([
                                html.H4("Student Name", className="card-title"),
                                dcc.Graph(figure=student_subplot),
                                
                            ])
                        ]
                    )
    
    app = JupyterDash()
    app.layout = html.Div([
            card
        ],
    )
    
    
    if __name__ == '__main__':
        app.run_server(host="0.0.0.0", port=8070, debug=True)
    

    enter image description here