diff --git a/AnimalShelter.py b/AnimalShelter.py new file mode 100644 index 0000000..f324400 --- /dev/null +++ b/AnimalShelter.py @@ -0,0 +1,125 @@ +# Cody Cook +# CS-340 Client/Server Development +# SNHU +# 2024/04/02 +# Project One + +# Import necessary libraries +from pymongo import MongoClient +from pymongo.errors import ConnectionFailure, OperationFailure +from urllib.parse import quote_plus +from bson.objectid import ObjectId + +# Define the AnimalShelter class +class AnimalShelter(): + """ CRUD operations for Animal collection in MongoDB """ + + # Initialize the MongoClient and the database + def __init__(self, username, password, hostname, port, database, collection): + """ Initialize the MongoClient and the database """ + + connection = "mongodb://%s:%s@%s:%d" % (quote_plus(username), quote_plus(password), hostname, port) + + # Try to connect to the database + try: + self.client = MongoClient(connection) # The client object is an instance of the MongoClient class + self.database = self.client['%s' % (database)] # The database object is an instance of the database class + self.collection = self.database['%s' % (collection)] # The collection object is an instance of the collection class + + # If the connection fails, print an error message + except ConnectionFailure as e: + # If the connection fails, print an error message + print("Could not connect to MongoDB: %s" % e) + +# Create method + def create(self, data): + """ Create a new animal in the database """ + + if data is not None: + # Try to insert the data into the database + try: + self.collection.insert_one(data) # Insert the data into the collection + print("Animal added successfully") # Print a success message + return True # Return True if successful + + # If the operation fails, print an error message + except OperationFailure as e: + # If the operation fails, print an error message + print("Could not add animal: %s" % e) + # Return False if the operation fails + return False + else: + # If the data is empty, print an error message + print("Data is empty, nothing to add") + # Return False if the data is empty + return False + +# Read method + def read(self, data): + """ Read an animal from the database """ + + if data is not None: + # Try to read the data from the database + try: + # Read the data from the collection + documents = list(self.collection.find(data, {"_id": False})) + # Return the documents + return documents + + # If the operation fails, print an error message + except OperationFailure as e: + # Print an error message + print("Could not read animal: %s" % e) + # Return an empty list + return [] + else: + # If the data is empty, print an error message + print("Data is empty, nothing to read") + # Return an empty list + return [] + +# Update method + def update(self, query, data): + """ Update an animal in the database """ + + # Check if the query and data are not empty + if query is not None and data is not None: + # Try to update the data in the database + try: + # Update the data in the collection + result = self.collection.update_many(query, {'$set': data}) + # Print a success message + print(f"Animals updated: {result.modified_count}") + # Return the number of animals updated + return result.modified_count + except OperationFailure as e: + # If the operation fails, print an error message + print(f"Could not update animal: {e}") + return 0 + else: + # If the data or query is empty, print an error message and return 0 + print("Data and/or query is empty, nothing to update") + return 0 + +# Delete method + def delete(self, data): + """ Delete an animal from the database """ + # Check if the data is not empty + if data is not None: + # Try to delete the data from the database + try: + # Delete the data from the collection + result = self.collection.delete_many(data) + # Print a success message + print(f"Animals deleted: {result.deleted_count}") + # Return the number of animals deleted + return result.deleted_count + except OperationFailure as e: + # If the operation fails, print an error message and return 0 + print(f"Could not delete animal: {e}") + return 0 + else: + # If the data is empty, print an error message and return 0 + print("Data is empty, nothing to delete") + return 0 + \ No newline at end of file diff --git a/AnimalShelterTest.ipynb b/AnimalShelterTest.ipynb new file mode 100644 index 0000000..f059739 --- /dev/null +++ b/AnimalShelterTest.ipynb @@ -0,0 +1,292 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "f9cc3d73", + "metadata": {}, + "outputs": [], + "source": [ + "# Import the AnimalShelter class\n", + "from AnimalShelter import AnimalShelter" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "fe96019a", + "metadata": {}, + "outputs": [], + "source": [ + "# Configure the connection details\n", + "username = 'aacuser'\n", + "password = '1B@nana4U$'\n", + "hostname = 'nv-desktop-services.apporto.com'\n", + "port = 30909\n", + "database = 'AAC'\n", + "collection = 'animals'" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5685f758", + "metadata": {}, + "outputs": [], + "source": [ + "# Create test data\n", + "test_file= {\n", + " '':\"10001\",\n", + " 'age_upon_outcome':\"2 weeks\", \n", + " 'animal_id':\"TEST123\",\n", + " 'animal_type': \"Dog\", \n", + " 'breed':\"Hawaiian Labrador\", \n", + " 'color':\"Black\",\n", + " 'date_of_birth':\"2024-03-12\", \n", + " 'monthyear': \"2024-03-26 21:18:00\", \n", + " 'outcome_subtype':'Transfer',\n", + " 'outcome_type':\"GOAT\", \n", + " 'sex_upon_outcome':\"Intact Male\", \n", + " 'location_long':\"-97.4033754809296\",\n", + " 'location_lat':\"30.6664774192579\",\n", + " 'age_upon_outcome_in_weeks':\"50 years\", \n", + " \"name\": \"Bark Obama\",\n", + " \"datetime\": \"2024-03-26 21:18:00\",\n", + " \"status\": \"Available\"\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5bd5672a", + "metadata": {}, + "outputs": [], + "source": [ + "# Create a sample query\n", + "query = {\"breed\":\"Hawaiian Labrador\"}" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e43f344a", + "metadata": {}, + "outputs": [], + "source": [ + "# Initiate a failed connection to the MongoDB\n", + "shelter = AnimalShelter(\"username\", \"password\", hostname, port, database, collection)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4d770845", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Could not read animal: Authentication failed., full error: {'ok': 0.0, 'errmsg': 'Authentication failed.', 'code': 18, 'codeName': 'AuthenticationFailed'}\n", + "Check existing sample: []\n" + ] + } + ], + "source": [ + "# Check to see if the entry exists already \n", + "# This assumes this hasn't already been run before.\n", + "precheck = shelter.read(query)\n", + "print(\"Check existing sample:\", precheck)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "15b3d1bb", + "metadata": {}, + "outputs": [], + "source": [ + "# Initiate the connection to the MongoDB\n", + "shelter = AnimalShelter(username, password, hostname, port, database, collection);" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "7f73158c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Check existing sample: []\n" + ] + } + ], + "source": [ + "# Check to see if the entry exists already \n", + "# This assumes this hasn't already been run before.\n", + "precheck = shelter.read(query)\n", + "print(\"Check existing sample:\", precheck)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "38e43a66", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Animal added successfully\n" + ] + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Add the animal to the database\n", + "shelter.create(test_file)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "bb4ebdd0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Check post-add sample: [{'': '10001', 'age_upon_outcome': '2 weeks', 'animal_id': 'TEST123', 'animal_type': 'Dog', 'breed': 'Hawaiian Labrador', 'color': 'Black', 'date_of_birth': '2024-03-12', 'monthyear': '2024-03-26 21:18:00', 'outcome_subtype': 'Transfer', 'outcome_type': 'GOAT', 'sex_upon_outcome': 'Intact Male', 'location_long': '-97.4033754809296', 'location_lat': '30.6664774192579', 'age_upon_outcome_in_weeks': '50 years', 'name': 'Bark Obama', 'datetime': '2024-03-26 21:18:00', 'status': 'Available'}]\n" + ] + } + ], + "source": [ + "# Confirm the entry was added\n", + "postcheck = shelter.read(query)\n", + "print(\"Check post-add sample:\", postcheck)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "a822e964", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Animals updated: 1\n", + "Updated items: 1\n" + ] + } + ], + "source": [ + "# Update the test animal to an Adopted status\n", + "query = {\"animal_id\": \"TEST123\"}\n", + "updates = {\"status\": \"Adopted\"}\n", + "result = shelter.update(query, updates)\n", + "print(f\"Updated items: {result}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "5f5300c2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Result: [{'': '10001', 'age_upon_outcome': '2 weeks', 'animal_id': 'TEST123', 'animal_type': 'Dog', 'breed': 'Hawaiian Labrador', 'color': 'Black', 'date_of_birth': '2024-03-12', 'monthyear': '2024-03-26 21:18:00', 'outcome_subtype': 'Transfer', 'outcome_type': 'GOAT', 'sex_upon_outcome': 'Intact Male', 'location_long': '-97.4033754809296', 'location_lat': '30.6664774192579', 'age_upon_outcome_in_weeks': '50 years', 'name': 'Bark Obama', 'datetime': '2024-03-26 21:18:00', 'status': 'Adopted'}]\n" + ] + } + ], + "source": [ + "# Confirm the update took place\n", + "check = shelter.read(query)\n", + "print(f\"Result: {check}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "974092b3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Animals deleted: 1\n", + "Deleted result: 1\n" + ] + } + ], + "source": [ + "# Delete animals matching the query\n", + "result = shelter.delete(query)\n", + "print(f\"Deleted result: {result}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "aae82505", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Check for records: []\n" + ] + } + ], + "source": [ + "# Check for the deleted animal\n", + "check = shelter.read(query)\n", + "print(\"Check for records: \", check)\n" + ] + } + ], + "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 +} diff --git a/Dashboard.ipynb b/Dashboard.ipynb new file mode 100644 index 0000000..1160c79 --- /dev/null +++ b/Dashboard.ipynb @@ -0,0 +1,459 @@ +{ + "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 +} diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..266196c Binary files /dev/null and b/logo.png differ