krz/omaha-metro-blotter

Archive of police activity and ALPR surveillance across the Omaha metro. alpr archive omaha police surveillance

Commit 07fa226874

07fa226874a2d6a9502926038714f91cb087bb03

parent: 1627615c29

Verified · cmc

cmc <hello@cleberg.net> · 2024-01-24 19:38 UTC

add line graph and date range selector

Layout: unified · split

README.md +3 −2
@@ -1,4 +1,4 @@
1# Omaha Incidents 1# Omaha Crime Mapping & Analysis
2 2
3Data from the Omaha police department, used to analyze and visualize statistics. 3Data from the Omaha police department, used to analyze and visualize statistics.
4 4
@@ -22,4 +22,5 @@ sqlite2rest serve ./raw_data/ingress.db
22 22
23## Screenshots 23## Screenshots
24 24
25![](./screenshots/dashboard.png) 25![](./screenshots/dashboard_01.png)
26![](./screenshots/dashboard_02.png)
app.py +69 −26
@@ -1,7 +1,9 @@
1from dash import Dash, html, dcc, callback, Output, Input, dash_table 1from dash import Dash, html, dcc, callback, Output, Input, dash_table
2import plotly.express as px 2import plotly.express as px
3import pandas as pd 3import pandas as pd
4import numpy as np
4import sqlite3 5import sqlite3
6from datetime import datetime, date
5 7
6# Connect to database and query all incidents 8# Connect to database and query all incidents
7connection = sqlite3.connect("./raw_data/ingress.db") 9connection = sqlite3.connect("./raw_data/ingress.db")
@@ -9,44 +11,51 @@ cursor = connection.cursor()
9query = "SELECT * FROM incidents;" 11query = "SELECT * FROM incidents;"
10df = pd.read_sql_query(query, connection).sort_values(by="description") 12df = pd.read_sql_query(query, connection).sort_values(by="description")
11 13
12# Create custom YEAR column to use in dropdown 14# Replace empty cells with NaN
13df['year'] = df['date'].str[-4:] 15df = df.replace(r'^\s*$', np.nan, regex=True)
16
17# Convert date column to datetime
18# TODO: Create a combined datetime column in the db to load/convert here
19df["date"] = pd.to_datetime(df["date"])
14 20
15# Configure HTML layout 21# Configure HTML layout
16app = Dash(__name__) 22app = Dash(__name__)
17app.layout = html.Div(children = [ 23app.layout = html.Div(children = [
18 html.H1(children="Omaha Police Incidents", style={"textAlign":"center"}), 24 html.H1(children="Omaha Crime Mapping & Analysis", style={"textAlign":"center"}),
19 html.Div([ 25 html.Div([
20 html.H2(children="Totals per Category and Year", style={"textAlign":"center"}), 26 html.Div(className="flex",
21 dcc.Dropdown(df.sort_values("description").description.unique(), "INJURY", id="dropdown"), 27 children = [
22 dcc.Dropdown(df.sort_values("year").year.unique(), "2023", id="year-dropdown"), 28 html.P("Crime:"),
23 html.Hr(), 29 dcc.Dropdown(df.sort_values("description").description.unique(), "INJURY", id="dropdown"),
24 dash_table.DataTable(data=df.to_dict("records"), page_size=5, id="table"), 30 ]
25 html.H2(children="Map of Incidents per Category and Year", style={"textAlign":"center"}), 31 ),
26 dcc.Graph(id="map-graph") 32 html.Div(className="flex",
33 children = [
34 html.P("Date Range:"),
35 dcc.DatePickerRange(
36 id='date-picker-range',
37 min_date_allowed=date(2015, 1, 1),
38 max_date_allowed=date(2023, 12, 31),
39 start_date=date(2015, 1, 1),
40 end_date=date(2023,12,31)
41 ),
42 ]
43 ),
44 dcc.Graph(id="map-graph"),
45 dcc.Graph(id="line-graph"),
46 dash_table.DataTable(data=df.to_dict("records"), page_size=5, id="table")
27 ]) 47 ])
28]) 48])
29 49
30# Create table
31@callback(
32 Output("table", "data"),
33 Input("dropdown", "value"),
34 Input("year-dropdown", "value")
35)
36def update_table(description, year):
37 dff = df[df.year == year]
38 dff = dff.reset_index()
39 dff = dff[dff.description == description]
40 return dff.to_dict("records")
41
42# Create map 50# Create map
43@callback( 51@callback(
44 Output("map-graph", "figure"), 52 Output("map-graph", "figure"),
45 Input("dropdown", "value"), 53 Input("dropdown", "value"),
46 Input("year-dropdown", "value") 54 Input('date-picker-range', 'start_date'),
55 Input('date-picker-range', 'end_date')
47) 56)
48def update_map(description, year): 57def update_map(description, start_date, end_date):
49 dff = df[df.year == year] 58 dff = df[(df['date'] > start_date) & (df['date'] < end_date)]
50 dff = dff.reset_index() 59 dff = dff.reset_index()
51 dff = dff[dff.description == description] 60 dff = dff[dff.description == description]
52 61
@@ -54,7 +63,6 @@ def update_map(description, year):
54 dff, 63 dff,
55 lat="lat", 64 lat="lat",
56 lon="lon", 65 lon="lon",
57 color="description",
58 hover_name="description", 66 hover_name="description",
59 hover_data=["date", "time"], 67 hover_data=["date", "time"],
60 title="Incident Count by Coordinates", 68 title="Incident Count by Coordinates",
@@ -69,5 +77,40 @@ def update_map(description, year):
69 77
70 return fig 78 return fig
71 79
80# Create line graph
81@callback(
82 Output("line-graph", "figure"),
83 Input("dropdown", "value"),
84 Input('date-picker-range', 'start_date'),
85 Input('date-picker-range', 'end_date')
86)
87def update_line(description, start_date, end_date):
88 dff = df[(df['date'] > start_date) & (df['date'] < end_date)]
89 dff = dff.reset_index()
90 dff = dff[dff.description == description]
91 dff = dff.groupby(by="date").count()
92 dff = dff.reset_index()
93
94 fig = px.line(
95 dff,
96 x="date",
97 y="description",
98 )
99
100 return fig
101
102# Create table
103@callback(
104 Output("table", "data"),
105 Input("dropdown", "value"),
106 Input('date-picker-range', 'start_date'),
107 Input('date-picker-range', 'end_date')
108)
109def update_table(description, start_date, end_date):
110 dff = df[(df['date'] > start_date) & (df['date'] < end_date)]
111 dff = dff.reset_index()
112 dff = dff[dff.description == description]
113 return dff.to_dict("records")
114
72if __name__ == "__main__": 115if __name__ == "__main__":
73 app.run(debug=True) 116 app.run(debug=True)
assets/styles.css +28 −3
@@ -4,6 +4,31 @@ body {
4 margin: 2rem auto; 4 margin: 2rem auto;
5} 5}
6 6
7hr { 7.dash-table-container {
8 margin: 1rem auto; 8 overflow: scroll;
9}\ No newline at end of file 9}
10
11.dash-table-container,
12.dash-graph {
13 margin: 2rem 0;
14 max-width: 100%;
15}
16
17.flex {
18 display: flex;
19 flex-direction: row;
20 align-items: center;
21}
22
23.flex p {
24 margin-right: 1rem;
25 flex-shrink: 0;
26}
27
28.flex .dash-dropdown {
29 width: 100%;
30}
31
32.flex #date-picker-range {
33 width: 100%;
34}
screenshots/dashboard.png deleted

Binary file not shown.

screenshots/dashboard_01.png added

Binary file not shown.

screenshots/dashboard_02.png added

Binary file not shown.