Py.Cafe

code-spd/

plotly-figurefriday-y2025w24

NYC 2023 Parking and Camera Violations

DocsPricing
  • assets/
  • data/
  • layout/
  • app.py
  • models.py
  • requirements.txt
  • utils.py
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import dash_mantine_components as dmc
from dash import Dash, dcc, callback, Output, Input, State, ctx, ALL
from dash.exceptions import PreventUpdate

from utils import VIOLATION_REGISTRY, set_custom_template_as_default

from layout.config import FONT_BODY, BACKGROUND_COLOR
from layout.header import app_header
from layout.selector import item_selector
from layout.summary import summary_section_children
from layout.visualizations import plotly_heat_map, visualization_group, legend_stack_children


set_custom_template_as_default()


# DATA
# -----------------------------------------------------------------------------
index = 0
initial_v = VIOLATION_REGISTRY[index]


# CONTENTS
# -----------------------------------------------------------------------------
center_col = dmc.Stack(
    children=[
        app_header(color='yellow'),
        dmc.Space(h=100),

        dmc.Group(
            item_selector(initial_v.label, color='yellow'),
            id='code-selector',
            justify='flex-start'
        ),
        dmc.Group(
            summary_section_children(initial_v, color='yellow'),
            id='summary-section',
            align='start',
            justify='space-between',
        ),
        dmc.Space(h=100),

        visualization_group(initial_v)
    ],
    gap=0,
    pt=0
)

main = dmc.Grid(
    children=[
        dmc.GridCol([], span=2.5),
        dmc.GridCol([center_col],span=7),
        dmc.GridCol([], span=2.5),
    ],
    gutter=0,
)


# LAYOUT
# -----------------------------------------------------------------------------
layout = dmc.AppShell([

    dmc.AppShellMain(
        children=[
            dcc.Store(id='store-selected', data={'index': index}),
            main,
        ],
        bg=BACKGROUND_COLOR
    ),
])


# APP
# -----------------------------------------------------------------------------
app = Dash(external_stylesheets=["https://fonts.googleapis.com/css2?family=Anonymous+Pro:ital,wght@0,400;0,700;1,400;1,700&family=Montserrat+Alternates:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"])
app.title = 'FigureFriday Y25W24'
app.layout = dmc.MantineProvider(
    children=layout,
    forceColorScheme="dark",
    theme = {
        'primaryColor': 'gray',
        'fontFamily': FONT_BODY,
    },
)


# CALLBACKS
# -----------------------------------------------------------------------------
@callback(
    Output('store-selected', 'data'),
    Input({'type': 'select-code-button', 'index': ALL}, 'n_clicks'),
    Input({'type': 'increment-code-button', 'index': ALL}, 'n_clicks'),
    State('store-selected', 'data')
)
def select_code(_, __, store_data):
    triggered_id = ctx.triggered_id
    if triggered_id is None:
        raise PreventUpdate
    
    store_index = store_data['index']
    t_index = triggered_id['index']
    t_type = triggered_id['type']

    if t_type == 'increment-code-button':
        if t_index == '-':
            store_index -= 1
            store_index = len(VIOLATION_REGISTRY)-1 if store_index < 0 else store_index
        else:
            store_index += 1
            store_index = 0 if store_index >= len(VIOLATION_REGISTRY) else store_index
    else:
        store_index = int(t_index)

    new_store_data = {'index': store_index}
    return new_store_data


@callback(
    Output('code-selector', 'children'),
    Output('summary-section', 'children'),
    Output('figure-heatmap', 'figure'),
    Output('figure-waterfall', 'data'),
    Output('figure-donut', 'data'),
    Output('legend-donut', 'children'),
    Output('group-visualizations', 'style'),
    Input('store-selected', 'data'),
)
def update_data(store_data):
    v = VIOLATION_REGISTRY[store_data['index']]

    visibility = {"display": "none"} if v.total_count==0 else {"display": "flex"}
    
    return (item_selector(v.label, color='yellow'),
            summary_section_children(v, color='yellow'),
            plotly_heat_map(v),
            v.get_waterfall_data(),
            v.get_hearing_data(),
            legend_stack_children(v),
            visibility
            )

    
# SERVER
# -----------------------------------------------------------------------------
if __name__ == "__main__":
    app.run(debug=False)