{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "659038cc", "metadata": {}, "outputs": [], "source": [ "# Setup the Jupyter version of Dash\n", "from jupyter_dash import JupyterDash" ] }, { "cell_type": "code", "execution_count": 2, "id": "1653e615", "metadata": {}, "outputs": [], "source": [ "# Configure the necessary Python module imports\n", "import dash_leaflet as dl\n", "from dash import dcc\n", "from dash import html\n", "import plotly.express as px\n", "from dash import dash_table\n", "from dash.dependencies import Input, Output, State\n", "import base64" ] }, { "cell_type": "code", "execution_count": 3, "id": "b86503df", "metadata": {}, "outputs": [], "source": [ "# Configure OS routines\n", "import os" ] }, { "cell_type": "code", "execution_count": 4, "id": "f21674e5", "metadata": {}, "outputs": [], "source": [ "# Configure the plotting routines\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "code", "execution_count": 5, "id": "47fcde56", "metadata": {}, "outputs": [], "source": [ "# import the CRUD module\n", "from AnimalShelter import AnimalShelter" ] }, { "cell_type": "code", "execution_count": 6, "id": "0f0ff9f4", "metadata": {}, "outputs": [], "source": [ "# Instantiate the CRUD module\n", "username = 'aacuser'\n", "password = '1B@nana4U$'\n", "hostname = 'nv-desktop-services.apporto.com'\n", "port = 30909\n", "database = 'AAC'\n", "collection = 'animals'\n", "db = AnimalShelter(username, password, hostname, port, database, collection)" ] }, { "cell_type": "code", "execution_count": 7, "id": "03f59f24", "metadata": {}, "outputs": [], "source": [ "# populate df with a dataframe of the items from the CRUD module\n", "df = pd.DataFrame.from_records(db.read({}))" ] }, { "cell_type": "code", "execution_count": 8, "id": "49395346", "metadata": {}, "outputs": [], "source": [ "# Drop _id; but I actually already do this in the CRUD so I will ignore it\n", "\n", "#df.drop(columns=['_id'],inplace=True)" ] }, { "cell_type": "code", "execution_count": 9, "id": "4159b76b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10000\n", "Index(['rec_num', 'age_upon_outcome', 'animal_id', 'animal_type', 'breed',\n", " 'color', 'date_of_birth', 'datetime', 'monthyear', 'name',\n", " 'outcome_subtype', 'outcome_type', 'sex_upon_outcome', 'location_lat',\n", " 'location_long', 'age_upon_outcome_in_weeks'],\n", " dtype='object')\n" ] } ], "source": [ "# Debugging to confirm records pulled\n", "print(len(df.to_dict(orient='records')))\n", "print(df.columns)" ] }, { "cell_type": "code", "execution_count": 10, "id": "3ed24c36", "metadata": {}, "outputs": [], "source": [ "# build the layout\n", "app = JupyterDash(__name__)\n", "image_filename = 'logo.png' \n", "header1 = \"CS-340 Dashboard by Cody Cook\"\n", "encoded_image = base64.b64encode(open(image_filename, 'rb').read())\n", "\n", "# define filter options\n", "filter_options = [\n", " {'label': 'Water Rescue', 'value': 'water'},\n", " {'label': 'Mountain or Wilderness Rescue', 'value': 'mountain'},\n", " {'label': 'Disaster or Individual Tracking', 'value': 'disaster'},\n", " {'label': 'Reset', 'value': 'reset'}\n", "]\n", "\n", "app.layout = html.Div([\n", " html.Div([\n", " html.Img(\n", " # show image and set size to 100x100\n", " src='data:image/png;base64,{}'.format(encoded_image.decode()),\n", " style={'height': '100px', 'width': '100px', 'display': 'block', 'margin-left': 'auto', 'margin-right': 'auto'}\n", " ),\n", " # show header1\n", " html.H1(header1, style={'textAlign': 'center'}),\n", " ],\n", " style={'text-align': 'center'}\n", " ),\n", " html.Hr(),\n", " html.Div(\n", " dcc.Dropdown(\n", " id='filter-type',\n", " options=[\n", " # show the filter options\n", " {'label': 'Water Rescue', 'value': 'water'},\n", " {'label': 'Mountain or Wilderness Rescue', 'value': 'mountain'},\n", " {'label': 'Disaster or Individual Tracking', 'value': 'disaster'},\n", " {'label': 'Reset', 'value': 'reset'}\n", " ],\n", " value='reset', \n", " clearable=False \n", " )),\n", " html.Hr(),\n", " dash_table.DataTable(\n", " id='datatable-id',\n", " columns=[{\n", " \"name\": i, \n", " \"id\": i, \n", " \"deletable\": False, \n", " \"selectable\": True\n", " } for i in df.columns],\n", " data=df.to_dict('records'),\n", " editable=False,\n", " filter_action=\"native\",\n", " sort_action=\"native\",\n", " sort_mode=\"multi\",\n", " column_selectable=\"single\",\n", " row_selectable=\"single\",\n", " page_action=\"native\",\n", " page_current=0,\n", " page_size=10,\n", " selected_columns=[df.columns[0]]\n", " ),\n", " html.Br(),\n", " html.Hr(),\n", " html.Div(className='row',\n", " style={'display' : 'flex'},\n", " children=[\n", " html.Div(\n", " # show the graph\n", " id='graph-id',\n", " className='col s12 m6',\n", "\n", " ),\n", " html.Div(\n", " # show the map\n", " id='map-id',\n", " className='col s12 m6',\n", " )\n", " ])\n", "])\n", "\n" ] }, { "cell_type": "code", "execution_count": 11, "id": "8b29d287", "metadata": {}, "outputs": [], "source": [ "@app.callback(\n", " Output('datatable-id', 'data'),\n", " [Input('filter-type', 'value')]\n", ")\n", "\n", "def update_dashboard(filter_type):\n", " # define what happens when the filter_type changes\n", " if filter_type == 'water':\n", " query = {\n", " \"breed\": {\"$in\": [\n", " \"Labrador Retriever Mix\", \n", " \"Chesapeake Bay Retriever\", \n", " \"Newfoundland\"]},\n", " \"sex_upon_outcome\": \"Intact Female\",\n", " \"age_upon_outcome_in_weeks\": {\n", " \"$gte\": 26, \n", " \"$lte\": 156}\n", " }\n", " elif filter_type == 'mountain':\n", " query = {\n", " \"breed\": {\"$in\": [\n", " \"German Shepherd\", \n", " \"Alaskan Malamute\", \n", " \"Old English Sheepdog\", \n", " \"Siberian Husky\", \n", " \"Rottweiler\"]},\n", " \"sex_upon_outcome\": \"Intact Male\",\n", " \"age_upon_outcome_in_weeks\": {\n", " \"$gte\": 26, \n", " \"$lte\": 156}\n", " }\n", " elif filter_type == 'disaster':\n", " query = {\n", " \"breed\": {\"$in\": [\n", " \"Doberman Pinscher\", \n", " \"German Shepherd\", \n", " \"Golden Retriever\", \n", " \"Bloodhound\", \n", " \"Rottweiler\"]},\n", " \"sex_upon_outcome\": \"Intact Male\",\n", " \"age_upon_outcome_in_weeks\": {\n", " \"$gte\": 20, \n", " \"$lte\": 300}\n", " }\n", " else:\n", " # leave it unfiltered\n", " query = {}\n", "\n", " # Fetch data using CRUD module and query\n", " filtered_df = pd.DataFrame.from_records(db.read(query))\n", " \n", " # Ensure the '_id' column is not passed to the DataTable to prevent rendering issues\n", " # I readded this and manually drop the _id without errors in case it already doesn't exist\n", " filtered_df.drop(columns=['_id'], inplace=True, errors='ignore')\n", "\n", " # Convert the df to a compatible dictionary format\n", " return filtered_df.to_dict('records')\n" ] }, { "cell_type": "code", "execution_count": 12, "id": "f91a318d", "metadata": {}, "outputs": [], "source": [ "# Display the breed of animal based on quantity represented in the data table\n", "@app.callback(\n", " Output('graph-id', \"children\"),\n", " [Input('datatable-id', \"derived_virtual_data\")]\n", ")\n", "\n", "def update_graphs(viewData):\n", " # return nothing if the data is empty\n", " if not viewData:\n", " return None\n", " \n", " # if there are too many breeds, limit with \"other\"\n", " dff = pd.DataFrame.from_dict(viewData)\n", " breed_counts = dff['breed'].value_counts()\n", " top_breeds = breed_counts.head(10)\n", " other_count = breed_counts.iloc[10:].sum()\n", " pie_title = \"Top 10 Breeds\"\n", "\n", " # Check if other_count is greater than 0\n", " if other_count > 0:\n", " # Create a Series for Other to concatenate with top_breeds\n", " other_series = pd.Series({'Other': other_count})\n", " # Use concat to merge the top breeds with the 'Other' series\n", " chart_data = pd.concat([top_breeds, other_series], axis=0)\n", " pie_title = \"Top 10 Breeds + Other\"\n", " else:\n", " # If 'other_count' is 0 or less, only use top_breeds\n", " chart_data = top_breeds\n", "\n", "\n", " chart_data = chart_data.reset_index()\n", " chart_data.columns = ['Breed', 'Count']\n", " \n", " # Generate a pie chart\n", " fig = px.pie(chart_data, values='Count', names='Breed', title=pie_title)\n", " \n", " return dcc.Graph(figure=fig)" ] }, { "cell_type": "code", "execution_count": 13, "id": "4c3537de", "metadata": {}, "outputs": [], "source": [ "# Highlight a selected column\n", "@app.callback(\n", " Output('datatable-id', 'style_data_conditional'),\n", " [Input('datatable-id', 'selected_columns')]\n", ")\n", "\n", "def update_styles(selected_columns):\n", " return [{\n", " 'if': { 'column_id': i },\n", " 'background_color': '#D2F3FF'\n", " } for i in selected_columns]" ] }, { "cell_type": "code", "execution_count": 14, "id": "d1c440a0", "metadata": {}, "outputs": [], "source": [ "# Update the geo-location chart with the selected item\n", "@app.callback(\n", " Output('map-id', \"children\"),\n", " [Input('datatable-id', \"derived_virtual_data\"),\n", " Input('datatable-id', \"derived_virtual_selected_rows\")]\n", ")\n", "\n", "def update_map(viewData, index):\n", " # If viewData is empty or index is not set \n", " if viewData is None or not index:\n", " # Center on Austin, TX\n", " return [\n", " dl.Map(\n", " style={\n", " 'width': '1000px', \n", " 'height': '500px'\n", " },\n", " center=[30.66, -97.40], \n", " zoom=10, \n", " children=[dl.TileLayer(id=\"base-layer-id\")])\n", " ]\n", " \n", " # Convert viewData to DataFrame\n", " dff = pd.DataFrame.from_dict(viewData)\n", " row = index[0]\n", "\n", " # Latitude and longitude for the selected item\n", " lat = dff.iloc[row, dff.columns.get_loc('location_lat')]\n", " lon = dff.iloc[row, dff.columns.get_loc('location_long')]\n", "\n", " # Update map centering dynamically based on the selected item's location\n", " return [\n", " dl.Map(\n", " style={\n", " 'width': '1000px', \n", " 'height': '500px'\n", " }, \n", " # reposition the map\n", " center=[lat, lon], \n", " zoom=10, \n", " children=[\n", " dl.TileLayer(id=\"base-layer-id\"),\n", " dl.Marker(\n", " # drop a point on the map\n", " position=[lat, lon], \n", " children=[\n", " dl.Tooltip(\n", " # show a tooltip on mouseover\n", " dff.iloc[row, dff.columns.get_loc('breed')]\n", " ),\n", " dl.Popup([\n", " # show a popup of the animal's name\n", " html.H1(\"Animal Name\"),\n", " html.P(dff.iloc[row, dff.columns.get_loc('name')]) \n", " ]) \n", " ])\n", " ])\n", " ]\n" ] }, { "cell_type": "code", "execution_count": 15, "id": "eefb074c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Dash app running on http://127.0.0.1:30088/\n" ] } ], "source": [ "app.run_server(debug=True)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.12" } }, "nbformat": 4, "nbformat_minor": 5 }