├── README.md └── wineDash ├── app.py ├── database ├── transforms.py └── wine_data.sqlite ├── hostedindex.py ├── index.py └── tabs ├── sidepanel.py ├── tab1.py └── tab2.py /README.md: -------------------------------------------------------------------------------- 1 | # DashApp 2 | Wine Dash App 3 | 4 | If following the hosting tutorial, use the hostedindex.py file. It has a couple modifications used to host the dash app on a vps. 5 | 6 | https://levelup.gitconnected.com/the-easiest-way-to-host-a-multi-page-dashboard-using-python-dash-and-linux-for-beginners-78a78e821ba 7 | 8 | -------------------------------------------------------------------------------- /wineDash/app.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import dash_bootstrap_components as dbc 3 | 4 | app = dash.Dash(__name__, external_stylesheets = [dbc.themes.BOOTSTRAP]) 5 | 6 | server = app.server 7 | app.config.suppress_callback_exceptions = True -------------------------------------------------------------------------------- /wineDash/database/transforms.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import dash_bootstrap_components as dbc 3 | import dash_core_components as dcc 4 | import dash_html_components as html 5 | import pandas as pd 6 | import sqlite3 7 | import dash 8 | from dash.dependencies import Input, Output 9 | import dash_table 10 | import pandas as pd 11 | 12 | conn = sqlite3.connect(r"C:\Users\MTGro\Desktop\coding\wineApp\db\wine_data.sqlite") 13 | c = conn.cursor() 14 | 15 | df = pd.read_sql("select * from wine_data", conn) 16 | 17 | df = df[['country','description','rating','price','province','title','variety','winery','color','varietyID']] 18 | -------------------------------------------------------------------------------- /wineDash/database/wine_data.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendgame/DashApp/95701cfbdea021956348c5b4856440c573402cf6/wineDash/database/wine_data.sqlite -------------------------------------------------------------------------------- /wineDash/hostedindex.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import plotly 3 | import dash_core_components as dcc 4 | import dash_html_components as html 5 | import dash_table 6 | from dash.dependencies import Input, Output 7 | import dash_bootstrap_components as dbc 8 | 9 | from tabs import sidepanel, tab1, tab2 10 | from database import transforms 11 | 12 | import sqlite3 13 | import pandas as pd 14 | import plotly.graph_objs as go 15 | 16 | 17 | from flask import Flask 18 | 19 | server = Flask(__name__) 20 | 21 | app = dash.Dash(__name__,server = server, meta_tags=[{ "content": "width=device-width"}], external_stylesheets=[dbc.themes.BOOTSTRAP]) 22 | 23 | 24 | app.config.suppress_callback_exceptions = True 25 | 26 | app.layout = sidepanel.layout 27 | 28 | @app.callback(Output('tabs-content', 'children'), 29 | [Input('tabs', 'value')]) 30 | def render_content(tab): 31 | if tab == 'tab-1': 32 | return tab1.layout 33 | elif tab == 'tab-2': 34 | return tab2.layout 35 | 36 | 37 | operators = [['ge ', '>='], 38 | ['le ', '<='], 39 | ['lt ', '<'], 40 | ['gt ', '>'], 41 | ['ne ', '!='], 42 | ['eq ', '='], 43 | ['contains '], 44 | ['datestartswith ']] 45 | 46 | 47 | def split_filter_part(filter_part): 48 | for operator_type in operators: 49 | for operator in operator_type: 50 | if operator in filter_part: 51 | name_part, value_part = filter_part.split(operator, 1) 52 | name = name_part[name_part.find('{') + 1: name_part.rfind('}')] 53 | 54 | value_part = value_part.strip() 55 | v0 = value_part[0] 56 | if (v0 == value_part[-1] and v0 in ("'", '"', '`')): 57 | value = value_part[1: -1].replace('\\' + v0, v0) 58 | else: 59 | try: 60 | value = float(value_part) 61 | except ValueError: 62 | value = value_part 63 | 64 | # word operators need spaces after them in the filter string, 65 | # but we don't want these later 66 | return name, operator_type[0].strip(), value 67 | 68 | return [None] * 3 69 | 70 | 71 | @app.callback( 72 | Output('table-sorting-filtering', 'data') 73 | , [Input('table-sorting-filtering', "page_current") 74 | , Input('table-sorting-filtering', "page_size") 75 | , Input('table-sorting-filtering', 'sort_by') 76 | , Input('table-sorting-filtering', 'filter_query') 77 | , Input('rating-95', 'value') 78 | , Input('price-slider', 'value') 79 | ]) 80 | def update_table(page_current, page_size, sort_by, filter, ratingcheck, prices): 81 | filtering_expressions = filter.split(' && ') 82 | dff = transforms.df 83 | print(ratingcheck) 84 | 85 | low = prices[0] 86 | high = prices[1] 87 | 88 | dff = dff.loc[(dff['price'] >= low) & (dff['price'] <= high)] 89 | 90 | if ratingcheck == ['Y']: 91 | dff = dff.loc[dff['rating'] >= 95] 92 | else: 93 | dff 94 | 95 | for filter_part in filtering_expressions: 96 | col_name, operator, filter_value = split_filter_part(filter_part) 97 | 98 | if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'): 99 | # these operators match pandas series operator method names 100 | dff = dff.loc[getattr(dff[col_name], operator)(filter_value)] 101 | elif operator == 'contains': 102 | dff = dff.loc[dff[col_name].str.contains(filter_value)] 103 | elif operator == 'datestartswith': 104 | # this is a simplification of the front-end filtering logic, 105 | # only works with complete fields in standard format 106 | dff = dff.loc[dff[col_name].str.startswith(filter_value)] 107 | 108 | if len(sort_by): 109 | dff = dff.sort_values( 110 | [col['column_id'] for col in sort_by], 111 | ascending=[ 112 | col['direction'] == 'asc' 113 | for col in sort_by 114 | ], 115 | inplace=False 116 | ) 117 | 118 | page = page_current 119 | size = page_size 120 | return dff.iloc[page * size: (page + 1) * size].to_dict('records') 121 | 122 | @app.callback(Output('table-paging-with-graph-container', "children"), 123 | [Input('rating-95', 'value') 124 | , Input('price-slider', 'value') 125 | ]) 126 | 127 | def update_graph(ratingcheck, prices): 128 | dff = transforms.df 129 | 130 | low = prices[0] 131 | high = prices[1] 132 | 133 | dff = dff.loc[(dff['price'] >= low) & (dff['price'] <= high)] 134 | 135 | if ratingcheck == ['Y']: 136 | dff = dff.loc[dff['rating'] >= 95] 137 | else: 138 | dff 139 | 140 | trace1 = go.Scattergl(x = dff['rating'] 141 | , y = dff['price'] 142 | , mode='markers' 143 | , opacity=0.7 144 | , marker={ 145 | 'size': 8 146 | , 'line': {'width': 0.5, 'color': 'white'} 147 | } 148 | , name='Price v Rating' 149 | ) 150 | return html.Div([ 151 | dcc.Graph( 152 | id='rating-price' 153 | , figure={ 154 | 'data': [trace1 155 | 156 | ], 157 | 'layout': dict( 158 | xaxis={'type': 'log', 'title': 'Rating'}, 159 | yaxis={'title': 'Price'}, 160 | margin={'l': 40, 'b': 40, 't': 10, 'r': 10}, 161 | legend={'x': 0, 'y': 1}, 162 | hovermode='closest' 163 | ) 164 | } 165 | ) 166 | ]) 167 | 168 | if __name__ == '__main__': 169 | app.run_server(debug = True, host = '0.0.0.0', port = 8000) 170 | -------------------------------------------------------------------------------- /wineDash/index.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import plotly 3 | import dash_core_components as dcc 4 | import dash_html_components as html 5 | import dash_table 6 | from dash.dependencies import Input, Output 7 | import dash_bootstrap_components as dbc 8 | 9 | from app import app 10 | from tabs import sidepanel, tab1, tab2 11 | from database import transforms 12 | import sqlite3 13 | import dash 14 | from dash.dependencies import Input, Output 15 | import dash_table 16 | import pandas as pd 17 | 18 | from app import app 19 | from tabs import sidepanel, tab1, tab2 20 | from database import transforms 21 | 22 | app.layout = sidepanel.layout 23 | 24 | @app.callback(Output('tabs-content', 'children'), 25 | [Input('tabs', 'value')]) 26 | def render_content(tab): 27 | if tab == 'tab-1': 28 | return tab1.layout 29 | elif tab == 'tab-2': 30 | return tab2.layout 31 | 32 | 33 | operators = [['ge ', '>='], 34 | ['le ', '<='], 35 | ['lt ', '<'], 36 | ['gt ', '>'], 37 | ['ne ', '!='], 38 | ['eq ', '='], 39 | ['contains '], 40 | ['datestartswith ']] 41 | 42 | 43 | def split_filter_part(filter_part): 44 | for operator_type in operators: 45 | for operator in operator_type: 46 | if operator in filter_part: 47 | name_part, value_part = filter_part.split(operator, 1) 48 | name = name_part[name_part.find('{') + 1: name_part.rfind('}')] 49 | 50 | value_part = value_part.strip() 51 | v0 = value_part[0] 52 | if (v0 == value_part[-1] and v0 in ("'", '"', '`')): 53 | value = value_part[1: -1].replace('\\' + v0, v0) 54 | else: 55 | try: 56 | value = float(value_part) 57 | except ValueError: 58 | value = value_part 59 | 60 | # word operators need spaces after them in the filter string, 61 | # but we don't want these later 62 | return name, operator_type[0].strip(), value 63 | 64 | return [None] * 3 65 | 66 | 67 | @app.callback( 68 | Output('table-sorting-filtering', 'data') 69 | , [Input('table-sorting-filtering', "page_current") 70 | , Input('table-sorting-filtering', "page_size") 71 | , Input('table-sorting-filtering', 'sort_by') 72 | , Input('table-sorting-filtering', 'filter_query') 73 | , Input('rating-95', 'value') 74 | , Input('price-slider', 'value') 75 | ]) 76 | def update_table(page_current, page_size, sort_by, filter, ratingcheck, prices): 77 | filtering_expressions = filter.split(' && ') 78 | dff = transforms.df 79 | print(ratingcheck) 80 | 81 | low = prices[0] 82 | high = prices[1] 83 | 84 | dff = dff.loc[(dff['price'] >= low) & (dff['price'] <= high)] 85 | 86 | if ratingcheck == ['Y']: 87 | dff = dff.loc[dff['rating'] >= 95] 88 | else: 89 | dff 90 | 91 | for filter_part in filtering_expressions: 92 | col_name, operator, filter_value = split_filter_part(filter_part) 93 | 94 | if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'): 95 | # these operators match pandas series operator method names 96 | dff = dff.loc[getattr(dff[col_name], operator)(filter_value)] 97 | elif operator == 'contains': 98 | dff = dff.loc[dff[col_name].str.contains(filter_value)] 99 | elif operator == 'datestartswith': 100 | # this is a simplification of the front-end filtering logic, 101 | # only works with complete fields in standard format 102 | dff = dff.loc[dff[col_name].str.startswith(filter_value)] 103 | 104 | if len(sort_by): 105 | dff = dff.sort_values( 106 | [col['column_id'] for col in sort_by], 107 | ascending=[ 108 | col['direction'] == 'asc' 109 | for col in sort_by 110 | ], 111 | inplace=False 112 | ) 113 | 114 | page = page_current 115 | size = page_size 116 | return dff.iloc[page * size: (page + 1) * size].to_dict('records') 117 | 118 | if __name__ == '__main__': 119 | app.run_server(debug = True) -------------------------------------------------------------------------------- /wineDash/tabs/sidepanel.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import plotly 3 | import dash_core_components as dcc 4 | import dash_html_components as html 5 | import dash_bootstrap_components as dbc 6 | import dash_table 7 | import pandas 8 | from dash.dependencies import Input, Output 9 | 10 | from app import app 11 | 12 | from tabs import tab1, tab2 13 | from database import transforms 14 | 15 | df = transforms.df 16 | min_p=df.price.min() 17 | max_p=df.price.max() 18 | 19 | layout = html.Div([ 20 | html.H1('Wine Dash') 21 | ,dbc.Row([dbc.Col( 22 | html.Div([ 23 | html.H2('Filters') 24 | , dcc.Checklist(id='rating-95' 25 | , options = [ 26 | {'label':'Only rating >= 95 ', 'value':'Y'} 27 | ]) 28 | ,html.Div([html.H5('Price Slider') 29 | ,dcc.RangeSlider(id='price-slider' 30 | ,min = min_p 31 | ,max= max_p 32 | , marks = {0: '$0', 33 | 500: '$500', 34 | 1000: '$1000', 35 | 1500: '$1500', 36 | 2000: '$2000', 37 | 2500: '$2500', 38 | 3000: '$3000', 39 | } 40 | , value = [0,3300] 41 | ) 42 | 43 | ]) 44 | 45 | ], style={'marginBottom': 50, 'marginTop': 25, 'marginLeft':15, 'marginRight':15}) 46 | , width=3) 47 | 48 | ,dbc.Col(html.Div([ 49 | dcc.Tabs(id="tabs", value='tab-1', children=[ 50 | dcc.Tab(label='Data Table', value='tab-1'), 51 | dcc.Tab(label='Scatter Plot', value='tab-2'), 52 | ]) 53 | , html.Div(id='tabs-content') 54 | ]), width=9)]) 55 | 56 | ]) 57 | -------------------------------------------------------------------------------- /wineDash/tabs/tab1.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import plotly 3 | import dash_core_components as dcc 4 | import dash_html_components as html 5 | import dash_bootstrap_components as dbc 6 | import dash_table 7 | import pandas as pd 8 | from dash.dependencies import Input, Output 9 | 10 | from app import app 11 | from database import transforms 12 | 13 | df = transforms.df 14 | 15 | PAGE_SIZE = 50 16 | # html.Div([ 17 | # dbc.Row([dbc.Col(html.Div(html.P("A single, half-width column")),style = {'padding':'50px'}) 18 | # ,dbc.Col( 19 | layout =html.Div(dash_table.DataTable( 20 | id='table-sorting-filtering', 21 | columns=[ 22 | {'name': i, 'id': i, 'deletable': True} for i in df[['country','description','rating','price','province','title','variety','winery','color']] 23 | ], 24 | style_table={'height':'750px' 25 | ,'overflowX': 'scroll'}, 26 | 27 | style_data_conditional=[ 28 | { 29 | 'if': {'row_index': 'odd'}, 30 | 'backgroundColor': 'rgb(248, 248, 248)' 31 | } 32 | ], 33 | style_cell={ 34 | 'height': '90', 35 | # all three widths are needed 36 | 'minWidth': '140px', 'width': '140px', 'maxWidth': '140px', 'textAlign': 'left' 37 | ,'whiteSpace': 'normal' 38 | } 39 | ,style_cell_conditional=[ 40 | {'if': {'column_id': 'description'}, 41 | 'width': '48%'}, 42 | {'if': {'column_id': 'title'}, 43 | 'width': '18%'}, 44 | ] 45 | , page_current= 0, 46 | page_size= PAGE_SIZE, 47 | page_action='custom', 48 | 49 | filter_action='custom', 50 | filter_query='', 51 | 52 | sort_action='custom', 53 | sort_mode='multi', 54 | sort_by=[] 55 | ) 56 | ) 57 | # , width=9) 58 | # ]) 59 | # ]) -------------------------------------------------------------------------------- /wineDash/tabs/tab2.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import dash_core_components as dcc 3 | import dash_html_components as html 4 | import dash_bootstrap_components as dbc 5 | import pandas as pd 6 | import plotly.graph_objs as go 7 | from dash.dependencies import Input, Output 8 | import dash_table 9 | from app import app 10 | from database import transforms 11 | 12 | df = transforms.df 13 | 14 | layout = html.Div( 15 | id='table-paging-with-graph-container', 16 | className="five columns" 17 | ) 18 | 19 | @app.callback(Output('table-paging-with-graph-container', "children"), 20 | [Input('rating-95', 'value') 21 | , Input('price-slider', 'value') 22 | ]) 23 | 24 | def update_graph(ratingcheck, prices): 25 | dff = df 26 | 27 | low = prices[0] 28 | high = prices[1] 29 | 30 | dff = dff.loc[(dff['price'] >= low) & (dff['price'] <= high)] 31 | 32 | if ratingcheck == ['Y']: 33 | dff = dff.loc[dff['rating'] >= 95] 34 | else: 35 | dff 36 | 37 | trace1 = go.Scattergl(x = dff['rating'] 38 | , y = dff['price'] 39 | , mode='markers' 40 | , opacity=0.7 41 | , marker={ 42 | 'size': 8 43 | , 'line': {'width': 0.5, 'color': 'white'} 44 | } 45 | , name='Price v Rating' 46 | ) 47 | return html.Div([ 48 | dcc.Graph( 49 | id='rating-price' 50 | , figure={ 51 | 'data': [trace1 52 | # dict( 53 | # x=df['price'], 54 | # y=df['rating'], 55 | # #text=df[df['continent'] == i]['country'], 56 | # mode='markers', 57 | # opacity=0.7, 58 | # marker={ 59 | # 'size': 8, 60 | # 'line': {'width': 0.5, 'color': 'white'} 61 | # }, 62 | # name='Price v Rating' 63 | #) 64 | ], 65 | 'layout': dict( 66 | xaxis={'type': 'log', 'title': 'Rating'}, 67 | yaxis={'title': 'Price'}, 68 | margin={'l': 40, 'b': 40, 't': 10, 'r': 10}, 69 | legend={'x': 0, 'y': 1}, 70 | hovermode='closest' 71 | ) 72 | } 73 | ) 74 | ]) --------------------------------------------------------------------------------