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
22
33Data from the Omaha police department, used to analyze and visualize statistics.
44
@@ -22,4 +22,5 @@ sqlite2rest serve ./raw_data/ingress.db
2222
2323## Screenshots
2424
25![](./screenshots/dashboard.png)
25![](./screenshots/dashboard_01.png)
26![](./screenshots/dashboard_02.png)
app.py +69 −26
@@ -1,7 +1,9 @@
11from dash import Dash, html, dcc, callback, Output, Input, dash_table
22import plotly.express as px
33import pandas as pd
4import numpy as np
45import sqlite3
6from datetime import datetime, date
57
68# Connect to database and query all incidents
79connection = sqlite3.connect("./raw_data/ingress.db")
@@ -9,44 +11,51 @@ cursor = connection.cursor()
911query = "SELECT * FROM incidents;"
1012df = pd.read_sql_query(query, connection).sort_values(by="description")
1113
12# Create custom YEAR column to use in dropdown
13df['year'] = df['date'].str[-4:]
14# Replace empty cells with NaN
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"])
1420
1521# Configure HTML layout
1622app = Dash(__name__)
1723app.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"}),
1925 html.Div([
20 html.H2(children="Totals per Category and Year", style={"textAlign":"center"}),
21 dcc.Dropdown(df.sort_values("description").description.unique(), "INJURY", id="dropdown"),
22 dcc.Dropdown(df.sort_values("year").year.unique(), "2023", id="year-dropdown"),
23 html.Hr(),
24 dash_table.DataTable(data=df.to_dict("records"), page_size=5, id="table"),
25 html.H2(children="Map of Incidents per Category and Year", style={"textAlign":"center"}),
26 dcc.Graph(id="map-graph")
26 html.Div(className="flex",
27 children = [
28 html.P("Crime:"),
29 dcc.Dropdown(df.sort_values("description").description.unique(), "INJURY", id="dropdown"),
30 ]
31 ),
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")
2747 ])
2848])
2949
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
4250# Create map
4351@callback(
4452 Output("map-graph", "figure"),
4553 Input("dropdown", "value"),
46 Input("year-dropdown", "value")
54 Input('date-picker-range', 'start_date'),
55 Input('date-picker-range', 'end_date')
4756)
48def update_map(description, year):
49 dff = df[df.year == year]
57def update_map(description, start_date, end_date):
58 dff = df[(df['date'] > start_date) & (df['date'] < end_date)]
5059 dff = dff.reset_index()
5160 dff = dff[dff.description == description]
5261
@@ -54,7 +63,6 @@ def update_map(description, year):
5463 dff,
5564 lat="lat",
5665 lon="lon",
57 color="description",
5866 hover_name="description",
5967 hover_data=["date", "time"],
6068 title="Incident Count by Coordinates",
@@ -69,5 +77,40 @@ def update_map(description, year):
6977
7078 return fig
7179
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
72115if __name__ == "__main__":
73116 app.run(debug=True)
assets/styles.css +28 −3
@@ -4,6 +4,31 @@ body {
44 margin: 2rem auto;
55}
66
7hr {
8 margin: 1rem auto;
9}
\ No newline at end of file
7.dash-table-container {
8 overflow: scroll;
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.