Guest User

Main

a guest
Nov 19th, 2023
46
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.23 KB | None | 0 0
  1.  
  2. from jupyter_plotly_dash import JupyterDash
  3.  
  4. import dash
  5. import dash_leaflet as dl
  6. import dash_core_components as dcc
  7. import dash_html_components as html
  8. import plotly.express as px
  9. import dash_table as dt
  10. from dash.dependencies import Input, Output, State
  11.  
  12. import os
  13. import numpy as np
  14. import pandas as pd
  15. from pymongo import MongoClient
  16. from bson.json_util import dumps
  17. import base64
  18. import logging
  19.  
  20. #importing AnimalShelter_CRUD File
  21. from AnimalShelter_CRUD import AnimalShelter
  22.  
  23.  
  24. ###########################
  25. #Configuring Logging Feature
  26. ###########################
  27.  
  28. logging.basicConfig(
  29. filename='Application.log', # Specify the log file name
  30. level=logging.INFO, # Set the logging level to INFO (you can change it)
  31. format='%(asctime)s - %(levelname)s - %(message)s' # Define log message format
  32. )
  33.  
  34. ###########################
  35. # Data Manipulation / Model
  36. ###########################
  37.  
  38. username = "aacuser"
  39. password = "admin"
  40. shelter = AnimalShelter(username, password)
  41.  
  42.  
  43. # class read method must support return of cursor object
  44. df = pd.DataFrame.from_records(shelter.read({}))
  45.  
  46.  
  47. #########################
  48. # Dashboard Layout / View
  49. #########################
  50. app = JupyterDash('SimpleExample')
  51.  
  52. # Add in Grazioso Salvare logo
  53. image_filename = 'Grazioso_Salvare_Logo.png'
  54. encoded_image = base64.b64encode(open(image_filename, 'rb').read())
  55.  
  56. # Place the HTML image tag in the line below into the app.layout code according to your design
  57. # Also remember to include a unique identifier such as your name or date
  58.  
  59.  
  60. #html.H3('SNHU CS-499 Dashboard'),
  61.  
  62. app.layout = html.Div([
  63. # html.Div(id='hidden-div', style={'display':'none'}),
  64. # Display an image
  65. html.Center(html.Img(src='data:image/png;base64,{}'.format(encoded_image.decode()))),
  66. # Display a title
  67. html.Center(html.B(html.H1('SNHU CS-340 Dashboard'))),
  68. # Display a description
  69. html.Center(html.P('Select up to five from the table for the map')),
  70. # Add a horizontal line
  71. html.Hr(),
  72. # Radio Items to select the rescue filter options
  73. html.Div(
  74. dcc.RadioItems(
  75. id='filter-type',
  76. # created the labels and keys based on the Grazioso requirements
  77. options=[
  78. {'label': 'Water Rescue', 'value': 'WR'},
  79. {'label': 'Mountain/Wilderness Rescue', 'value': 'MWR'},
  80. {'label': 'Disaster Rescue/Individual Tracking', 'value': 'DRIT'},
  81. {'label': 'Reset - returns unfiltered state', 'value': 'RESET'}
  82. ],
  83. value='RESET',
  84. labelStyle={'display': 'inline-block'}
  85. )
  86. ),
  87. # Add another horizontal line
  88. html.Hr(),
  89. # Data table component
  90. dt.DataTable(
  91. id='datatable-id',
  92. columns=[
  93. {"name": i, "id": i, "deletable": False, "selectable": True} for i in df.columns
  94. ],
  95.  
  96. data=df.to_dict('records'),
  97.  
  98. #made selectable 'multi' to allow map to work with several options
  99.  
  100. editable=False,
  101. filter_action="native",
  102. sort_action="native",
  103. sort_mode="multi",
  104. column_selectable=False,
  105. row_selectable="multi",
  106. row_deletable=False,
  107. selected_columns=[],
  108. selected_rows=[],
  109. page_action="native",
  110. page_current=0,
  111. page_size=10,
  112. ),
  113.  
  114. # Add line break and horizontal line
  115. html.Br(),
  116. html.Hr(),
  117.  
  118. #This sets up the dashboard so that the chart and geolocation chart are side-by-side
  119.  
  120. # Div to hold charts and map
  121. html.Div(className='row',
  122. style={'display' : 'flex'},
  123. children=[
  124. # Chart component
  125. html.Div(
  126. #dcc.Graph(id='graph-id'),
  127. id='graph-id',
  128. className='col s12 m6',
  129.  
  130. ),
  131. # Map component
  132. html.Div(
  133. id='map-id',
  134. className='col s12 m6',
  135. )
  136. ])
  137. ])
  138.  
  139. #############################################
  140. # Interaction Between Components / Controller
  141. #############################################
  142.  
  143.  
  144. @app.callback([Output('datatable-id','data'),
  145. Output('datatable-id','columns')],
  146. [Input('filter-type', 'value')])
  147. def update_dashboard(filter_type):
  148. try:
  149. logging.info(f"Updating dashboard with filter type: {filter_type}")
  150.  
  151. if filter_type == 'WR':
  152. # Filter criteria for intact female dogs of specific breeds and ages
  153. filter_criteria = {
  154. '$and': [
  155. {'sex_upon_outcome': 'Intact Female'},
  156. {'$or': [
  157. {'breed': 'Labrador Retriever Mix'},
  158. {'breed': 'Chesa Bay Retr Mix'},
  159. {'breed': 'Newfoundland Mix'},
  160. {'breed': 'Newfoundland/Labrador Retriever'},
  161. {'breed': 'Newfoundland/Australian Cattle Dog'},
  162. {'breed': 'Newfoundland/Great Pyrenees'}
  163. ]},
  164. {'$and': [
  165. {'age_upon_outcome_in_weeks': {'$gte': 26}},
  166. {'age_upon_outcome_in_weeks': {'$lte': 156}}
  167. ]}
  168. ]
  169. }
  170. df = pd.DataFrame(list(shelter.read(filter_criteria)))
  171.  
  172. elif filter_type == 'MWR':
  173. # Filter criteria for intact male dogs of specific breeds and ages
  174. filter_criteria = {
  175. '$and': [
  176. {'sex_upon_outcome': 'Intact Male'},
  177. {'$or': [
  178. {'breed': 'German Shepherd'},
  179. {'breed': 'Alaskan Malamute'},
  180. {'breed': 'Old English Sheepdog'},
  181. {'breed': 'Rottweiler'},
  182. {'breed': 'Siberian Husky'}
  183. ]},
  184. {'$and': [
  185. {'age_upon_outcome_in_weeks': {'$gte': 26}},
  186. {'age_upon_outcome_in_weeks': {'$lte': 156}}
  187. ]}
  188. ]
  189. }
  190. df = pd.DataFrame(list(shelter.read(filter_criteria)))
  191.  
  192. elif filter_type == 'DRIT':
  193. # Filter criteria for intact male dogs of specific breeds and ages
  194. filter_criteria = {
  195. '$and': [
  196. {'sex_upon_outcome': 'Intact Male'},
  197. {'$or': [
  198. {'breed': 'Doberman Pinscher'},
  199. {'breed': 'German Shepherd'},
  200. {'breed': 'Golden Retriever'},
  201. {'breed': 'Bloodhound'},
  202. {'breed': 'Rottweiler'}
  203. ]},
  204. {'$and': [
  205. {'age_upon_outcome_in_weeks': {'$gte': 20}},
  206. {'age_upon_outcome_in_weeks': {'$lte': 300}}
  207. ]}
  208. ]
  209. }
  210. df = pd.DataFrame(list(shelter.read(filter_criteria)))
  211.  
  212. elif filter_type == 'RESET':
  213. # Reset the search to display all results
  214. df = pd.DataFrame.from_records(shelter.read({}))
  215.  
  216. # Define columns for the DataTable
  217. columns = [{"name": i, "id": i, "deletable": False, "selectable": True} for i in df.columns]
  218.  
  219. # Convert DataFrame to dictionary records
  220. data = df.to_dict('records')
  221.  
  222. return (data, columns)
  223.  
  224. except Exception as e:
  225. # Handle exceptions and provide an error message
  226. logging.error(f"An error occurred in update_dashboard: {str(e)}")
  227. error_message = str(e)
  228. return ([], []), [{'name': 'Error', 'id': 'error'}] # Return an error message as a single-column DataTable
  229.  
  230.  
  231. @app.callback(
  232. Output('datatable-id', 'style_data_conditional'), # Output component to update (conditional styling)
  233. [Input('datatable-id', 'selected_columns')] # Input component (selected columns)
  234. )
  235. def update_styles(selected_columns):
  236. """
  237. Callback function to update the conditional styling of a DataTable based on selected columns.
  238.  
  239. Args:
  240. - selected_columns (list): A list of selected column IDs in the DataTable.
  241.  
  242. Returns:
  243. - list of dict: A list of dictionaries specifying conditional styling rules for columns.
  244. """
  245. # This callback function is triggered when the user selects columns in the DataTable.
  246.  
  247. # Define conditional styling rules for selected columns.
  248. # Each rule is specified as a dictionary in a list comprehension.
  249. try:
  250. if isinstance(selected_columns, list):
  251. logging.info(f"Updating styles for selected columns: {selected_columns}")
  252. return [{
  253. 'if': { 'column_id': i }, # Apply the style if the condition is met for column 'i'
  254. 'background_color': '#D2F3FF' # Set the background color for the selected columns
  255. } for i in selected_columns]
  256. else:
  257. # Handle the case where 'selected_columns' is not a list
  258. raise ValueError("Input 'selected_columns' must be a list.")
  259. except Exception as e:
  260. logging.error(f"An error occurred in update_styles: {str(e)}")
  261. # Handle any unexpected exceptions
  262. return [] # Return an empty list if an error occurs
  263.  
  264.  
  265. #function to update the pie chart
  266. @app.callback(
  267. Output('graph-id', "children"), # Output component to update with new content (a graph)
  268. [Input('datatable-id', "derived_viewport_data")]) # Input component for data (from a DataTable)
  269.  
  270. def update_graphs(viewData):
  271. try:
  272.  
  273. # Log the function call
  274. logging.info("Updating pie chart")
  275.  
  276. # Check if viewData is not None and is a dictionary
  277. if viewData is not None and isinstance(viewData, dict):
  278.  
  279. # Import the currently displayed data from the DataTable into a DataFrame (dff)
  280. dff = pd.DataFrame.from_dict(viewData)
  281.  
  282. # Check if 'breed' column exists in the DataFrame
  283. if 'breed' in dff.columns:
  284. # Calculate values needed for the pie chart: names (breed) and values (recurring counts)
  285. names = dff['breed'].value_counts().keys().tolist()
  286. values = dff['breed'].value_counts().tolist()
  287.  
  288. # Create a pie chart based on the calculated data
  289. pie_chart = dcc.Graph(
  290. figure=px.pie(
  291. data_frame=dff,
  292. values=values,
  293. names=names,
  294. color_discrete_sequence=px.colors.sequential.RdBu,
  295. width=800,
  296. height=500
  297. )
  298.  
  299. )
  300.  
  301. logging.error("Error: 'breed' column not found in the data.")
  302. # Return the pie chart as a list, which will be used to update the output component
  303. return [pie_chart]
  304. else:
  305. # Handle the case where 'breed' column is missing
  306. logging.error("Error: 'breed' column not found in the data.")
  307. return ["Error: 'breed' column not found in the data."]
  308. else:
  309. # Handle the case where viewData is not a dictionary
  310. logging.error("Error: Invalid data format.")
  311. return ["Error: Invalid data format."]
  312.  
  313. except Exception as e:
  314. # Handle any other unexpected exceptions
  315. logging.error(f"An error occurred in update_graphs: {str(e)}")
  316. return ["Error: An unexpected error occurred."]
  317.  
  318.  
  319. # This is a Dash callback decorator that specifies the output and inputs for the callback function.
  320. @app.callback(
  321. Output('map-id', "children"), # Output component to update with new content
  322. [Input('datatable-id', "derived_viewport_data"), # Input component for data
  323. Input('datatable-id', 'selected_rows'), # Input component for selected rows
  324. Input('datatable-id', 'selected_columns')]) # Input component for selected columns
  325.  
  326. def add_marker_with_popup(selected_row, dff):
  327. #"""
  328. #Add a marker with a popup to a map based on the selected row's data.
  329.  
  330. #Args:
  331. #- selected_row (int): Index of the selected row.
  332. #- dff (pd.DataFrame): The DataFrame containing the data.
  333.  
  334. #Returns:
  335. #- str: HTML code for the marker and popup.
  336. #"""
  337. try:
  338. if selected_row < len(dff):
  339.  
  340. # Log the function call
  341. logging.info(f"Adding marker for selected row: {selected_row}")
  342.  
  343. marker_html = f"""
  344. dl.Marker(position=({dff.iloc[selected_row, 13]}, {dff.iloc[selected_row, 14]}), children=[
  345. dl.Tooltip("{dff.iloc[selected_row, 4]}"),
  346. dl.Popup([
  347. html.H4("Animal Name"),
  348. html.P("{dff.iloc[selected_row, 9]}"),
  349. html.H4("Sex"),
  350. html.P("{dff.iloc[selected_row, 12]}"),
  351. html.H4("Breed"),
  352. html.P("{dff.iloc[selected_row, 4]}"),
  353. html.H4("Age"),
  354. html.P("{dff.iloc[selected_row, 15]}")
  355. ])
  356. ])
  357. """
  358. return marker_html
  359. else:
  360. raise IndexError("Selected row index is out of range.")
  361. except KeyError as e:
  362. # Handle missing key error
  363. logging.error(f"Error: Missing key - {str(e)}")
  364. return f"Error: Missing key - {str(e)}"
  365. except ValueError as e:
  366. # Handle value error
  367. logging.error(f"Error: Value error - {str(e)}")
  368. return f"Error: Value error - {str(e)}"
  369. except IndexError as e:
  370. # Handle index out of range error
  371. logging.error(f"Error: Index out of range - {str(e)}")
  372. return f"Error: Index out of range - {str(e)}"
  373.  
  374.  
  375. def update_map(viewData, selected_rows, selected_columns):
  376.  
  377. #"""
  378. #Updates Map with with pop up Markers
  379. #Args:
  380. #- viewData(Dictionary): Data stored in the form of dictionary in Database.
  381. #- selected_row (int): Index of the selected row.
  382.  
  383. #Returns:
  384. #- map_html: HTML code for the map.
  385. #"""
  386. try:
  387. # Log the function call
  388. logging.info("Updating map")
  389.  
  390. dff = pd.DataFrame.from_dict(viewData)
  391.  
  392. if selected_rows == []:
  393. selected_rows = [0]
  394.  
  395. # Create a map object
  396. map_html = """
  397. dl.Map(style={'width':'1000px', 'height': '500px'}, center=[30.75,-97.48], zoom=10, children=[
  398. dl.TileLayer(id="base-layer-id"),
  399. """
  400.  
  401. # Add markers to the map based on the selected rows
  402. for index in selected_rows:
  403. try:
  404. marker_html = add_marker_with_popup(index, dff)
  405. map_html += marker_html
  406. except Exception as e:
  407. # Handle any exceptions that may occur during marker addition
  408. logging.error(f"Error adding marker: {str(e)}")
  409. map_html += f"Error adding marker: {str(e)}"
  410.  
  411. # Close the map object
  412. map_html += "])"
  413.  
  414. return [map_html]
  415. except Exception as e:
  416. # Log the exception
  417. logging.error(f"An error occurred in update_map: {str(e)}")
  418. app
  419.  
Add Comment
Please, Sign In to add comment