From 07fa226874a2d6a9502926038714f91cb087bb03 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Wed, 24 Jan 2024 13:38:15 -0600 Subject: add line graph and date range selector --- README.md | 5 ++- app.py | 95 +++++++++++++++++++++++++++++++------------ assets/styles.css | 31 ++++++++++++-- screenshots/dashboard.png | Bin 2332467 -> 0 bytes screenshots/dashboard_01.png | Bin 0 -> 2140416 bytes screenshots/dashboard_02.png | Bin 0 -> 412054 bytes 6 files changed, 100 insertions(+), 31 deletions(-) delete mode 100644 screenshots/dashboard.png create mode 100644 screenshots/dashboard_01.png create mode 100644 screenshots/dashboard_02.png diff --git a/README.md b/README.md index 1ba973d..7289ae9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Omaha Incidents +# Omaha Crime Mapping & Analysis Data from the Omaha police department, used to analyze and visualize statistics. @@ -22,4 +22,5 @@ sqlite2rest serve ./raw_data/ingress.db ## Screenshots -![](./screenshots/dashboard.png) +![](./screenshots/dashboard_01.png) +![](./screenshots/dashboard_02.png) diff --git a/app.py b/app.py index ec05fcf..099c317 100644 --- a/app.py +++ b/app.py @@ -1,7 +1,9 @@ from dash import Dash, html, dcc, callback, Output, Input, dash_table import plotly.express as px import pandas as pd +import numpy as np import sqlite3 +from datetime import datetime, date # Connect to database and query all incidents connection = sqlite3.connect("./raw_data/ingress.db") @@ -9,44 +11,51 @@ cursor = connection.cursor() query = "SELECT * FROM incidents;" df = pd.read_sql_query(query, connection).sort_values(by="description") -# Create custom YEAR column to use in dropdown -df['year'] = df['date'].str[-4:] +# Replace empty cells with NaN +df = df.replace(r'^\s*$', np.nan, regex=True) + +# Convert date column to datetime +# TODO: Create a combined datetime column in the db to load/convert here +df["date"] = pd.to_datetime(df["date"]) # Configure HTML layout app = Dash(__name__) app.layout = html.Div(children = [ - html.H1(children="Omaha Police Incidents", style={"textAlign":"center"}), + html.H1(children="Omaha Crime Mapping & Analysis", style={"textAlign":"center"}), html.Div([ - html.H2(children="Totals per Category and Year", style={"textAlign":"center"}), - dcc.Dropdown(df.sort_values("description").description.unique(), "INJURY", id="dropdown"), - dcc.Dropdown(df.sort_values("year").year.unique(), "2023", id="year-dropdown"), - html.Hr(), - dash_table.DataTable(data=df.to_dict("records"), page_size=5, id="table"), - html.H2(children="Map of Incidents per Category and Year", style={"textAlign":"center"}), - dcc.Graph(id="map-graph") + html.Div(className="flex", + children = [ + html.P("Crime:"), + dcc.Dropdown(df.sort_values("description").description.unique(), "INJURY", id="dropdown"), + ] + ), + html.Div(className="flex", + children = [ + html.P("Date Range:"), + dcc.DatePickerRange( + id='date-picker-range', + min_date_allowed=date(2015, 1, 1), + max_date_allowed=date(2023, 12, 31), + start_date=date(2015, 1, 1), + end_date=date(2023,12,31) + ), + ] + ), + dcc.Graph(id="map-graph"), + dcc.Graph(id="line-graph"), + dash_table.DataTable(data=df.to_dict("records"), page_size=5, id="table") ]) ]) -# Create table -@callback( - Output("table", "data"), - Input("dropdown", "value"), - Input("year-dropdown", "value") -) -def update_table(description, year): - dff = df[df.year == year] - dff = dff.reset_index() - dff = dff[dff.description == description] - return dff.to_dict("records") - # Create map @callback( Output("map-graph", "figure"), Input("dropdown", "value"), - Input("year-dropdown", "value") + Input('date-picker-range', 'start_date'), + Input('date-picker-range', 'end_date') ) -def update_map(description, year): - dff = df[df.year == year] +def update_map(description, start_date, end_date): + dff = df[(df['date'] > start_date) & (df['date'] < end_date)] dff = dff.reset_index() dff = dff[dff.description == description] @@ -54,7 +63,6 @@ def update_map(description, year): dff, lat="lat", lon="lon", - color="description", hover_name="description", hover_data=["date", "time"], title="Incident Count by Coordinates", @@ -69,5 +77,40 @@ def update_map(description, year): return fig +# Create line graph +@callback( + Output("line-graph", "figure"), + Input("dropdown", "value"), + Input('date-picker-range', 'start_date'), + Input('date-picker-range', 'end_date') +) +def update_line(description, start_date, end_date): + dff = df[(df['date'] > start_date) & (df['date'] < end_date)] + dff = dff.reset_index() + dff = dff[dff.description == description] + dff = dff.groupby(by="date").count() + dff = dff.reset_index() + + fig = px.line( + dff, + x="date", + y="description", + ) + + return fig + +# Create table +@callback( + Output("table", "data"), + Input("dropdown", "value"), + Input('date-picker-range', 'start_date'), + Input('date-picker-range', 'end_date') +) +def update_table(description, start_date, end_date): + dff = df[(df['date'] > start_date) & (df['date'] < end_date)] + dff = dff.reset_index() + dff = dff[dff.description == description] + return dff.to_dict("records") + if __name__ == "__main__": app.run(debug=True) diff --git a/assets/styles.css b/assets/styles.css index d014936..97fd316 100644 --- a/assets/styles.css +++ b/assets/styles.css @@ -4,6 +4,31 @@ body { margin: 2rem auto; } -hr { - margin: 1rem auto; -} \ No newline at end of file +.dash-table-container { + overflow: scroll; +} + +.dash-table-container, +.dash-graph { + margin: 2rem 0; + max-width: 100%; +} + +.flex { + display: flex; + flex-direction: row; + align-items: center; +} + +.flex p { + margin-right: 1rem; + flex-shrink: 0; +} + +.flex .dash-dropdown { + width: 100%; +} + +.flex #date-picker-range { + width: 100%; +} diff --git a/screenshots/dashboard.png b/screenshots/dashboard.png deleted file mode 100644 index 5a7c6aa..0000000 Binary files a/screenshots/dashboard.png and /dev/null differ diff --git a/screenshots/dashboard_01.png b/screenshots/dashboard_01.png new file mode 100644 index 0000000..b0587e4 Binary files /dev/null and b/screenshots/dashboard_01.png differ diff --git a/screenshots/dashboard_02.png b/screenshots/dashboard_02.png new file mode 100644 index 0000000..620f2fb Binary files /dev/null and b/screenshots/dashboard_02.png differ -- cgit v1.2.3-70-g09d2