A Dash App With Visualization Interacting with AG Grid

 Let's look at a simple Dash application which has a visualization controlling Dash AG Grid. We will have a bar graph as visualization and we will filter data in the AG Grid when any bar in the bar chart is clicked. Here is a glimpse of the app:

The code for this sample application can be found at the public github repository -https://github.com/dvinayakn/interactive-visualization-ag-grid

Here is the code structure of the application:

Note that this application is not ready for deployment to Dash Enterprise. We will discuss that aspect in some other post. We have an "assets" folder where our sample data resides in the file - sales_data.csv inside the assets folder. Also, any javascript or css file placed inside the assets folder is directly accessible to the application. We have all our python code inside app.py for simplicity. In real world applications, the code is split into multiple python files and modules.

We import the necessary packages for our application :

from dash import Dash

from dash.dependencies import Input, Output

from dash.exceptions import PreventUpdate

from dash import dcc, html

import plotly.express as px

import dash_ag_grid as dag

import pandas as pd

Load the sample sales data in a pandas data frame:

# Load data from CSV file

sales_df = pd.read_csv('assets/sales_data.csv')

This data is at level - product, region and month. 

We need to aggregate the data at product and month level to display in the visualization. Let's aggregate:

# Aggregate this data at month and product level

sales_by_product_month_df = sales_df.groupby(by=['Product Name', 'Month Name']).aggregate({'Revenue':'sum'}).reset_index()

# Above statement is equivalent to below sql statement

# select "Product Name", "Month Name", sum(Revenue) as "Revenue"

# from sales

# group by "Product Name", "Month Name"

Create an instance of Dash

app = Dash(__name__)


Create a bar chart visualization with this data

# Let's create a simple visualization with our dataframe

revenue_by_product_month_chart = px.bar(

       data_frame=sales_by_product_month_df,

       x='Month Name',# Column from data_frame which will indicate values to be plotted on x axis

       y='Revenue',

# Column from data_frame which will indicate values to be plotted on y axis (measure or fact)

       color='Product Name',

# This will be the legend. Colored stacks will be governed by this column in data_frame

       custom_data = ['Product Name']

# This information will be included in the clickData when the bars in chart are clicked

   )

Then define the layout of our application. The layout contains the visualization created above and an empty html.Div object. The empty html.Div object will hold the Dash AG Grid when an interaction is made with the visualization.

app.layout = html.Div(
[
# HTML Div component to hold our graph.
# The graph will in turn contain a figure object
html.Div(
id='chart-container-div',
children=dcc.Graph(
id='bar-chart',
figure = revenue_by_product_month_chart
)
),

# This Div will hold the Dash AG Grid
# AG Grid is an advanced table rendered in Dash app.
# The html.Table object could be used as well. We will use AG Grid.
html.Div(
id='table-container-div',
style = {'display': 'flex', 'justifyContent': 'center'},
children=None
)
]
)

The "style" attribute of html.Div in above code snippet defines the inline style for the Div. This is a representation of standard css styles, with attributes converted to camel case. For example, justify-content in css becomes justifyContent in the inline style. This style can be referred from the css file placed in assets folder as well. The code in css file is commented as of now. If the inline style is removed and the css code is un-commented, this application will work just fine.

Now is the time to go through the callback defined to make the interactions possible. Callbacks are like event listeners and event handlers in Dash. You define which items should trigger the callback (referred to as Input), which items should be updated as a result of callback (referred to as Output) and which items should be available to the callback function as it is (referred to as State). We are not using "State" in this  application yet.

The callbacks are defined with the decorator "callback" available with the Dash object created above. The first argument to the callback, which reads as below, indicates that the "children" property of an html element with id "table-container-div" should be updated with whatever is returned by the callback function.

Output('table-container-div', 'children'),

The second argument to the callback, which reads as below, indicates that whenever "clickData" property of the chart with id "bar-chart" is updated, the following callback function should be called and results are to be assigned to "Output" of the callback.

Input('bar-chart','clickData'),

For a graph rendered on the page, there are several properties which get updated depending on user interaction with the graph. These include hoverData, selectedData, relayoutData and clickData. In our application, we want the callback to trigger whenever the user clicks on the bar chart. Hence we use "clickData".

The third argument to the callback, which reads as below indicates that the callback should not be called when the application loads for the first time. The callback function should only be called when properties of objects as mentioned in "Input" are modified.

prevent_initial_call = True # To prevent the callback to run when the

application loads


Let's look at the callback function itself. Here is an example of clickData for this application:

# {

   #   'points': [

   #         {

   #         'curveNumber': 2,

   #         'pointNumber': 7,

   #         'pointIndex': 7,

   #         'x': 'Mar 2022',

   #         'y': 47463,

   #         'label': 'Mar 2022',

   #         'value': 47463,

   #         'bbox': {

   #             'x0': 711.03,

   #             'x1': 781.23,

   #             'y0': 192.56,

   #             'y1': 192.56

   #             },

   #         'customdata': ['Product 3']

   #         }

   #   ]

   # }


Look at the 'customdata' attribute in above clickData. It is the product whose bar segment was clicked. 

Inside the callback function, we filter the data based on user interaction. We can determine from the clickData object, which month's bar was clicked and which product was clicked on the visualization. We extract these values and filter the data frame:

    #Extract the product and month which was clicked on the chart.

   product_clicked = click_data['points'][0]['customdata'][0]

   month_clicked = click_data['points'][0]['x']

   # Filter the sales_df with the selected product and month name

   filtered_df = sales_df[(sales_df['Product Name'] == product_clicked) & (    sales_df['Month Name'] == month_clicked)]


Then, create a new AG Grid object with the filtered data frame and return the same.

# Create and return a dash ag grid object with this data.

   return dag.AgGrid(

           enableEnterpriseModules=False,

           columnDefs=[{"headerName": column, "field": column} for column in filtered_df.columns],

           rowData= filtered_df.to_dict('records'),

           columnSize="sizeToFit",

           defaultColDef=dict(

               resizable=True,

           ),

           style = {'height': '250px', 'width': '60%'}

   )



Notes:
  1. Here is a great blog post for a simple “Hello World” Dash application, if you are looking to start with Dash.
  2. You need to get the AG Grid library installed for the code to work. Here is the command to get it installed:
            pip install dash-ag-grid==2.0.0a5
    
        This is the alpha release of AG Grid at time of this post. More on Dash AG Grid on the official portal - https://dash.plotly.com/dash-ag-grid


I hope you like this post.

Thank You!


Comments