Showing elements side by side in Dash Python

Hey,

I know that there have been numerous other posts about this, but I’m trying to solve this creating my own css style sheet.

The problem I am having is that I cannot make a table and a pie chart appear side by side in my dashboard. I’m very new to using dash and have just been practicing with a toy dataset from kaggle, so I am not doing it very well. I have tried to use a css grid system, specifying the width of column classes in my css style sheet. However, even if I make the width of the two items I am trying to align side by side significantly less than half the page, they still appear in a single column.

Here is my Python code:

import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
import plotly.express as px
import pandas as pd
import time

colours = {'background': '#161a28', 'text': '#f3f5f4'}


def build_banner():
    """Builds the banner for the top of the dashboard"""
    return html.Div(
        id="banner",
        className="banner",
        children=[
            html.Div(
                id="banner-text",
                children=[
                    html.H5(
                        children="VEHICLES SALES ANALYSIS",
                        id='banner-header'
                    ),
                    html.H6(
                        children="Sales detail broken down by product line",
                        id='banner-sub-header'
                    ),
                ],
            ),
        ],
    )


def generate_table(dataframe, max_rows=10):
    """Generates a dash HTML table from a pandas dataframe"""
    return html.Table(children=[
        html.Thead(
            html.Tr([html.Th(col) for col in dataframe.columns])
        ),
        html.Tbody([
            html.Tr([
                html.Td(dataframe.iloc[i][col]) for col in dataframe.columns
            ]) for i in range(min(len(dataframe), max_rows))
        ])],
        id='left-bottom-table'
    )


def build_date_slider():
    """Builds the slider for selecting a range of dates to
        which are used to update the graphs and tables"""
    return dcc.RangeSlider(
                id='month-slider',
                min=unix_time_ms(monthly_sales.index.min()),
                max=unix_time_ms(monthly_sales.index.max()),
                value=[unix_time_ms(monthly_sales.index.min()),
                       unix_time_ms(monthly_sales.index.max())],
                marks=get_marks(start=monthly_sales.index.min(),
                                end=monthly_sales.index.max()),
                step=None
                )


def unix_time_ms(dt):
    ''' Convert datetime to unix timestamp '''
    return int(time.mktime(dt.timetuple()))


def unix_to_datetime(unix):
    ''' Convert unix timestamp to datetime. '''
    return pd.to_datetime(unix, unit='s')


def get_marks(start, end):
    ''' Returns the marks for labeling range slider'''
    marks = {}
    daterange = pd.date_range(start, end, freq='M')
    for date in daterange:
        # Append value to dict
        marks[unix_time_ms(date)] = {'label': str(date.strftime('%Y-%m')),
                                     'style': {'color': '#f3f5f4'}}
    return marks


app = dash.Dash(__name__)

df = pd.read_csv('sales_data_sample.csv', encoding="ISO-8859-1")
df['DATE'] = df.apply(lambda row: str(row['YEAR_ID']) + '-' +
                      str(row['MONTH_ID']) + '-01', axis=1)
df['DATE'] = df['DATE'].apply(pd.to_datetime)

monthly_sales = pd.pivot_table(data=df,
                               index='DATE',
                               columns='PRODUCTLINE',
                               values='SALES',
                               aggfunc='sum')

df_for_table = df.groupby('PRODUCTLINE').sum()[['SALES', 'QUANTITYORDERED']]
df_for_table['Average price'] = (df_for_table['SALES']
                                 / df_for_table['QUANTITYORDERED'])
df_for_table = df_for_table.round(2).sort_values('SALES', ascending=False)
df_for_table.columns = ['Sales (USD)', 'Volume (Units)', 'ASP (USD)']
df_for_table.index.name = 'Product line'

app.layout = html.Div(
    id='main-container',
    className='main-container',
    children=[
        build_banner(),
        html.Div(
            id='app-container',
            children=[
                html.Div(
                    className='row',
                    children=[
                        html.Div(
                            className='twelve-columns',
                            children=[
                                dcc.Graph(className='graphs', id='sales-graph'),
                                build_date_slider()
                            ]
                        )
                    ]
                ),
                html.Div(
                    className='row',
                    children=[
                        html.Div(
                            className='six-columns',
                            children=[
                                generate_table(df_for_table.reset_index()),
                            ]
                        ),
                        html.Div(
                            className='six-columns',
                            children=[
                                dcc.Graph(className='graphs',
                                          id='right-bottom-pie')
                            ]
                        )
                    ]
                )
            ]
        )
    ]
)


@app.callback(
    Output('sales-graph', 'figure'),
    Output('left-bottom-table', 'children'),
    Output('right-bottom-pie', 'figure'),
    Input('month-slider', 'value'))
def update_graph(selected_period):
    # Filter the df for the graph
    filtered_graph = monthly_sales.loc[unix_to_datetime(selected_period[0]):
                                       unix_to_datetime(selected_period[1])]
    # Filter the df for the Table
    df_for_table = df[(df['DATE'] > unix_to_datetime(selected_period[0])) &
                      (df['DATE'] < unix_to_datetime(selected_period[1]))]

    df_for_table = df_for_table.groupby('PRODUCTLINE').sum()
    df_for_table = df_for_table[['SALES', 'QUANTITYORDERED']]
    df_for_table['Average price'] = (df_for_table['SALES']
                                     / df_for_table['QUANTITYORDERED'])
    df_for_table = df_for_table.round(2).sort_values('SALES', ascending=False)
    df_for_table.columns = ['Sales (USD)', 'Volume (Units)', 'ASP (USD)']
    df_for_table.index.name = 'Product line'
    df_for_table = df_for_table.reset_index()

    # Make bar graph
    fig = px.bar(data_frame=filtered_graph.reset_index(),
                 x='DATE',
                 y=['Classic Cars', 'Motorcycles', 'Planes',
                    'Ships', 'Trains', 'Trucks and Buses', 'Vintage Cars'],
                 labels={'value': 'Sales (USD)',
                         'variable': 'Product category',
                         'DATE': ''})
    fig.update_layout(barmode='stack',
                      plot_bgcolor=colours['background'],
                      paper_bgcolor=colours['background'],
                      font=dict(color=colours['text']))

    # Make Table
    tab = generate_table(df_for_table)

    # Make pie chart
    pie = px.pie(data_frame=df_for_table,
                 values='Sales (USD)',
                 names='Product line')
    pie.update_layout(plot_bgcolor=colours['background'],
                      paper_bgcolor=colours['background'],
                      font=dict(color=colours['text']))
    return fig, tab, pie


if __name__ == '__main__':
    app.run_server(debug=True)

And here is the relevent snippet from my css formatting:

.main-container {
  max-width: 100%;
  align-items: center;
  padding-left: 0px;
  padding-right: 0px;
  padding-top: 0px;
  padding-right: 0px;
  background-color: inherit;
}

#app-container {
  width: auto;
}

.twelve-columns {
  width: 96%;
}

.six-columns {
  width: 45%;
}

#left-bottom-table {
  color: #f3f5f4;
  background-color: #161a28;
  width: 100%;
  height: 350px;
  margin-top: 5px;
  margin-bottom: 5px;
}

#right-bottom-pie {
  margin-top: 5px;
  margin-bottom: 5px;
  heght: 350px;
}

This is what I get back in the dash board:

I really appreciate any help with this, thank you very much.

Hi,

You are missing defining rules for .row. It could be that

.row {
    display: table
}

is enough for you, but I am not an expert.

It is a bit of a hassle to define your own css if you don’t have some prior experience with it, so I would strongly recommend you to use dash-bootstrap-components for that (here are the docs on the grid specifically). It is well maintained and supported, even if it is not part of “core” Dash.

1 Like

Hey,

Thanks a lot for this. I tried display: table but it just made all the elements go really thin and bunch up on the left. I had a play around and setting display: flex seems to have done the trick.

There cetainly seems to be a lot to learn with CSS style sheets! I’m mostly just copying the Dash gallery examples and then playing around with it to suit my needs. I will check out bootstrap then, the syntax certainly looks much simpler. Thanks for the recommendation.

Tom

1 Like