├── .gitignore
├── LICENSE
├── README.md
├── part1
├── README.md
├── assets
│ └── style.css
├── data
│ └── transactions.csv
├── environment
│ ├── README.md
│ ├── conda.yaml
│ ├── requirements-dev.txt
│ └── requirements.txt
├── main.py
└── src
│ ├── __init__.py
│ └── components
│ ├── __init__.py
│ ├── bar_chart.py
│ ├── ids.py
│ ├── layout.py
│ └── nation_dropdown.py
├── part2
├── README.md
├── assets
│ └── style.css
├── data
│ └── transactions.csv
├── environment
│ ├── README.md
│ ├── conda.yaml
│ ├── requirements-dev.txt
│ └── requirements.txt
├── main.py
└── src
│ ├── __init__.py
│ ├── components
│ ├── __init__.py
│ ├── bar_chart.py
│ ├── category_dropdown.py
│ ├── ids.py
│ ├── layout.py
│ ├── month_dropdown.py
│ ├── pie_chart.py
│ └── year_dropdown.py
│ └── data
│ ├── __init__.py
│ └── loader.py
└── part3
├── README.md
├── assets
└── style.css
├── data
└── transactions.csv
├── environment
├── README.md
├── conda.yaml
├── requirements-dev.txt
└── requirements.txt
├── locale
├── category.en.yml
├── category.nl.yml
├── general.en.yml
└── general.nl.yml
├── main.py
└── src
├── __init__.py
├── components
├── __init__.py
├── bar_chart.py
├── category_dropdown.py
├── dropdown_helper.py
├── ids.py
├── layout.py
├── month_dropdown.py
├── pie_chart.py
└── year_dropdown.py
└── data
├── __init__.py
├── loader.py
└── source.py
/.gitignore:
--------------------------------------------------------------------------------
1 | **/*.pyc
2 | .coverage
3 | htmlcov
4 | **/.DS_Store
5 | **/*.log
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 ArjanCodes and Mark Todisco
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Dash tutorial series
2 |
3 | In this video series, I show you how to set up data visualization using Plotly Dash in Python in a few easy steps. This is a three-part miniseries. In part 1, I show the basics of Dash. In part 2 I dive more deeply into loading, filtering and displaying data. And finally in part 3, I cover a few ways to improve the design and make the application more ready for production.
4 |
5 | Video part 1: https://youtu.be/XOFrvzWFM7Y.
6 |
7 | Video part 2: https://youtu.be/GlRauKqI08Y.
8 |
9 | Video part 3: https://youtu.be/L_KlPZ5qBOU.
10 |
--------------------------------------------------------------------------------
/part1/README.md:
--------------------------------------------------------------------------------
1 | # Dash App Tutorial
2 |
3 | ## Running the app
4 |
5 | Run the following commands and open the local host web address chosen by Dash.
6 |
7 | ```shell
8 | python ./main.py
9 | ```
10 |
11 | An example of the expected terminal messages are shown below:
12 |
13 | ```shell
14 | Dash is running on http://127.0.0.1:8050/
15 |
16 | * Serving Flask app 'app' (lazy loading)
17 | * Environment: production
18 | WARNING: This is a development server. Do not use it in a production deployment.
19 | Use a production WSGI server instead.
20 | * Debug mode: on
21 | ```
22 |
--------------------------------------------------------------------------------
/part1/assets/style.css:
--------------------------------------------------------------------------------
1 | hr {
2 | border-top: 2px solid gray;
3 | }
4 |
5 | .dropdown-container {
6 | display: grid;
7 | grid-template-columns: repeat(3, 1fr);
8 | gap: 10px;
9 | }
10 |
11 | .dropdown-button {
12 | width: 100%;
13 | margin-top: 5px;
14 | }
15 |
16 | .tabs-group {
17 | padding-top: 20px;
18 | }
19 |
20 | .app-div {
21 | padding: 10px;
22 | }
23 |
24 | .data-table-div {
25 | margin: auto;
26 | padding: 10px;
27 | }
28 |
29 | .budget-label {
30 | font-size: 20px;
31 | font-weight: bold;
32 | }
33 |
34 | .budget-text {
35 | font-size: 20px;
36 | }
37 |
38 | .budget-text-value {
39 | font-size: 20px;
40 | font-style: italic;
41 | }
42 |
43 | .budget-progress-bar {
44 | height: 20px;
45 | }
46 |
47 | .budget-tooltip {
48 | font-size: 18px;
49 | }
50 |
51 | .checklist {
52 | text-align: center;
53 | padding-top: 10px;
54 | padding-bottom: 20px;
55 | }
56 |
57 | @media(max-width: 800px) {
58 | .dropdown-container {
59 | grid-template-columns: 1fr;
60 | }
61 | }
--------------------------------------------------------------------------------
/part1/data/transactions.csv:
--------------------------------------------------------------------------------
1 | date,amount,category
2 | 2020-01-01,89.92,groceries
3 | 2020-01-01,48.74,entertainment
4 | 2020-01-01,39.08,groceries
5 | 2020-01-01,48.36,groceries
6 | 2020-01-01,77.36,groceries
7 | 2020-01-01,73.46,groceries
8 | 2020-01-01,57.16,groceries
9 | 2020-01-01,75.94,groceries
10 | 2020-02-01,76.29,healthcare
11 | 2020-02-01,16.09,entertainment
12 | 2020-02-01,82.62,groceries
13 | 2020-02-01,21.61,utilities
14 | 2020-02-01,43.55,entertainment
15 | 2020-02-01,28.57,car
16 | 2020-02-01,86.28,groceries
17 | 2020-02-01,43.51,housing
18 | 2020-02-01,7.93,car
19 | 2020-03-01,51.83,groceries
20 | 2020-03-01,48.8,travel
21 | 2020-03-01,30.33,groceries
22 | 2020-03-01,85.14,groceries
23 | 2020-03-01,96.06,groceries
24 | 2020-03-01,66.16,housing
25 | 2020-03-01,53.73,groceries
26 | 2020-03-01,64.25,healthcare
27 | 2020-03-01,94.17,groceries
28 | 2020-04-01,65.61,housing
29 | 2020-04-01,11.49,travel
30 | 2020-04-01,88.06,groceries
31 | 2020-04-01,58.63,groceries
32 | 2020-04-01,28.14,groceries
33 | 2020-04-01,32.49,utilities
34 | 2020-04-01,6.55,groceries
35 | 2020-04-01,94.11,groceries
36 | 2020-05-01,1.19,groceries
37 | 2020-05-01,56.66,groceries
38 | 2020-05-01,49.05,entertainment
39 | 2020-05-01,41.46,healthcare
40 | 2020-05-01,71.48,entertainment
41 | 2020-05-01,32.84,groceries
42 | 2020-05-01,78.47,entertainment
43 | 2020-05-01,29.33,groceries
44 | 2020-06-01,87.67,groceries
45 | 2020-06-01,44.3,housing
46 | 2020-06-01,56.0,entertainment
47 | 2020-06-01,66.25,travel
48 | 2020-06-01,97.5,healthcare
49 | 2020-06-01,29.02,groceries
50 | 2020-06-01,97.86,groceries
51 | 2020-07-01,47.77,housing
52 | 2020-07-01,80.89,groceries
53 | 2020-07-01,21.09,utilities
54 | 2020-07-01,4.62,car
55 | 2020-07-01,96.13,utilities
56 | 2020-07-01,12.53,groceries
57 | 2020-07-01,35.77,entertainment
58 | 2020-08-01,82.73,healthcare
59 | 2020-08-01,47.77,groceries
60 | 2020-08-01,74.13,groceries
61 | 2020-08-01,38.33,entertainment
62 | 2020-08-01,65.55,housing
63 | 2020-08-01,6.29,groceries
64 | 2020-08-01,25.23,groceries
65 | 2020-09-01,49.51,travel
66 | 2020-09-01,17.92,entertainment
67 | 2020-09-01,74.69,travel
68 | 2020-09-01,89.4,groceries
69 | 2020-09-01,55.0,travel
70 | 2020-09-01,81.93,utilities
71 | 2020-09-01,57.69,healthcare
72 | 2020-09-01,22.22,entertainment
73 | 2020-09-01,13.22,entertainment
74 | 2020-09-01,77.76,groceries
75 | 2020-09-01,13.22,groceries
76 | 2020-10-01,57.58,groceries
77 | 2020-10-01,4.0,car
78 | 2020-10-01,20.48,utilities
79 | 2020-10-01,15.74,utilities
80 | 2020-10-01,59.31,entertainment
81 | 2020-10-01,15.31,groceries
82 | 2020-10-01,96.55,groceries
83 | 2020-10-01,91.65,entertainment
84 | 2020-10-01,50.04,groceries
85 | 2020-10-01,80.78,groceries
86 | 2020-11-01,71.81,groceries
87 | 2020-11-01,24.68,housing
88 | 2020-11-01,69.69,healthcare
89 | 2020-11-01,56.34,groceries
90 | 2020-12-01,66.58,entertainment
91 | 2020-12-01,76.24,utilities
92 | 2020-12-01,81.17,groceries
93 | 2020-12-01,47.01,car
94 | 2020-12-01,71.57,groceries
95 | 2020-12-01,73.81,utilities
96 | 2020-12-01,9.22,healthcare
97 | 2020-12-01,32.22,travel
98 | 2020-12-01,5.72,entertainment
99 | 2020-12-01,73.53,groceries
100 | 2021-01-01,70.84,entertainment
101 | 2021-01-01,99.42,entertainment
102 | 2021-01-01,37.04,healthcare
103 | 2021-01-01,90.98,healthcare
104 | 2021-01-01,58.67,healthcare
105 | 2021-01-01,92.68,groceries
106 | 2021-01-01,65.82,groceries
107 | 2021-01-01,99.58,groceries
108 | 2021-01-01,16.65,healthcare
109 | 2021-01-01,10.93,groceries
110 | 2021-02-01,33.45,groceries
111 | 2021-02-01,70.84,travel
112 | 2021-02-01,34.73,entertainment
113 | 2021-02-01,46.42,groceries
114 | 2021-02-01,91.4,healthcare
115 | 2021-02-01,71.29,travel
116 | 2021-02-01,21.49,entertainment
117 | 2021-02-01,18.48,groceries
118 | 2021-02-01,55.53,healthcare
119 | 2021-02-01,77.15,groceries
120 | 2021-03-01,43.56,groceries
121 | 2021-03-01,83.79,healthcare
122 | 2021-03-01,84.38,travel
123 | 2021-03-01,24.06,groceries
124 | 2021-03-01,56.91,groceries
125 | 2021-03-01,9.95,travel
126 | 2021-03-01,17.04,housing
127 | 2021-03-01,65.6,entertainment
128 | 2021-03-01,48.31,housing
129 | 2021-03-01,53.55,healthcare
130 | 2021-04-01,85.99,car
131 | 2021-04-01,59.48,travel
132 | 2021-04-01,1.1,travel
133 | 2021-04-01,54.48,groceries
134 | 2021-04-01,14.25,car
135 | 2021-04-01,24.01,groceries
136 | 2021-04-01,66.32,healthcare
137 | 2021-04-01,38.34,healthcare
138 | 2021-04-01,62.12,groceries
139 | 2021-04-01,14.14,housing
140 | 2021-04-01,64.07,groceries
141 | 2021-04-01,97.5,housing
142 | 2021-04-01,97.26,entertainment
143 | 2021-04-01,90.99,entertainment
144 | 2021-05-01,64.94,car
145 | 2021-05-01,36.64,groceries
146 | 2021-05-01,70.0,groceries
147 | 2021-05-01,90.62,groceries
148 | 2021-05-01,92.65,groceries
149 | 2021-05-01,77.18,groceries
150 | 2021-05-01,57.91,groceries
151 | 2021-05-01,43.15,housing
152 | 2021-06-01,83.8,groceries
153 | 2021-06-01,49.62,entertainment
154 | 2021-06-01,71.06,groceries
155 | 2021-06-01,33.25,groceries
156 | 2021-06-01,53.4,groceries
157 | 2021-06-01,4.61,entertainment
158 | 2021-06-01,2.51,utilities
159 | 2021-06-01,56.95,entertainment
160 | 2021-06-01,82.12,travel
161 | 2021-06-01,29.88,entertainment
162 | 2021-06-01,1.36,utilities
163 | 2021-07-01,40.01,housing
164 | 2021-07-01,47.98,groceries
165 | 2021-07-01,45.75,groceries
166 | 2021-07-01,95.4,groceries
167 | 2021-07-01,90.35,groceries
168 | 2021-07-01,82.36,groceries
169 | 2021-07-01,92.92,car
170 | 2021-07-01,16.85,entertainment
171 | 2021-07-01,82.77,groceries
172 | 2021-07-01,63.85,utilities
173 | 2021-07-01,10.82,entertainment
174 | 2021-08-01,42.11,groceries
175 | 2021-08-01,10.76,groceries
176 | 2021-08-01,49.48,entertainment
177 | 2021-08-01,53.11,groceries
178 | 2021-08-01,51.99,groceries
179 | 2021-08-01,75.16,utilities
180 | 2021-08-01,53.6,groceries
181 | 2021-08-01,37.27,groceries
182 | 2021-08-01,11.23,groceries
183 | 2021-09-01,45.43,groceries
184 | 2021-09-01,73.96,groceries
185 | 2021-09-01,45.1,groceries
186 | 2021-09-01,25.35,groceries
187 | 2021-09-01,68.04,groceries
188 | 2021-09-01,62.09,groceries
189 | 2021-09-01,66.98,utilities
190 | 2021-09-01,32.28,groceries
191 | 2021-09-01,19.8,entertainment
192 | 2021-09-01,69.23,groceries
193 | 2021-10-01,56.28,healthcare
194 | 2021-10-01,2.16,entertainment
195 | 2021-10-01,59.04,groceries
196 | 2021-10-01,89.95,groceries
197 | 2021-10-01,35.4,groceries
198 | 2021-10-01,75.07,entertainment
199 | 2021-10-01,34.26,groceries
200 | 2021-11-01,95.83,groceries
201 | 2021-11-01,9.02,utilities
202 | 2021-11-01,85.69,healthcare
203 | 2021-11-01,24.64,healthcare
204 | 2021-11-01,49.18,healthcare
205 | 2021-11-01,53.08,entertainment
206 | 2021-11-01,29.73,travel
207 | 2021-11-01,17.31,groceries
208 | 2021-11-01,76.38,groceries
209 | 2021-11-01,89.54,groceries
210 | 2021-12-01,53.16,groceries
211 | 2021-12-01,53.25,entertainment
212 | 2021-12-01,91.38,car
213 | 2021-12-01,77.33,entertainment
214 | 2021-12-01,85.64,groceries
215 | 2021-12-01,61.59,entertainment
216 | 2021-12-01,43.79,car
217 | 2021-12-01,44.01,travel
218 | 2021-12-01,13.41,healthcare
219 | 2022-01-01,96.68,housing
220 | 2022-01-01,18.45,groceries
221 | 2022-01-01,78.89,groceries
222 | 2022-01-01,6.87,healthcare
223 | 2022-01-01,56.18,entertainment
224 | 2022-01-01,63.06,groceries
225 | 2022-01-01,15.97,groceries
226 | 2022-01-01,50.25,groceries
227 | 2022-01-01,58.76,groceries
228 | 2022-01-01,24.09,groceries
229 | 2022-01-01,31.04,groceries
230 | 2022-01-01,70.04,groceries
231 | 2022-01-01,25.06,groceries
232 | 2022-01-01,61.61,travel
233 | 2022-01-01,39.21,groceries
234 | 2022-01-01,11.46,groceries
235 | 2022-02-01,14.8,housing
236 | 2022-02-01,81.78,healthcare
237 | 2022-02-01,12.49,entertainment
238 | 2022-02-01,68.73,utilities
239 | 2022-02-01,30.66,groceries
240 | 2022-02-01,56.6,groceries
241 | 2022-02-01,73.0,entertainment
242 | 2022-02-01,7.76,travel
243 | 2022-03-01,22.03,groceries
244 | 2022-03-01,74.89,groceries
245 | 2022-03-01,27.95,groceries
246 | 2022-03-01,97.88,travel
247 | 2022-03-01,29.95,entertainment
248 | 2022-03-01,33.46,entertainment
249 | 2022-03-01,11.64,entertainment
250 | 2022-03-01,28.7,groceries
251 | 2022-03-01,84.11,groceries
252 | 2022-03-01,65.94,healthcare
253 | 2022-03-01,68.89,groceries
254 | 2022-03-01,99.26,car
255 | 2022-03-01,19.47,car
256 | 2022-03-01,79.93,entertainment
257 | 2022-04-01,81.24,groceries
258 | 2022-04-01,32.05,utilities
259 | 2022-04-01,74.43,utilities
260 | 2022-04-01,92.54,entertainment
261 | 2022-04-01,67.87,entertainment
262 | 2022-04-01,49.54,groceries
263 | 2022-04-01,72.31,entertainment
264 | 2022-04-01,74.85,groceries
265 | 2022-04-01,56.05,groceries
266 | 2022-04-01,69.44,travel
267 | 2022-04-01,85.56,groceries
268 | 2022-05-01,14.99,healthcare
269 | 2022-05-01,29.46,groceries
270 | 2022-05-01,17.41,groceries
271 | 2022-05-01,83.34,groceries
272 | 2022-05-01,40.07,groceries
273 | 2022-05-01,78.19,entertainment
274 | 2022-05-01,32.96,housing
275 | 2022-05-01,89.92,entertainment
276 | 2022-06-01,86.82,groceries
277 | 2022-06-01,69.78,entertainment
278 | 2022-06-01,66.43,groceries
279 | 2022-06-01,58.21,groceries
280 | 2022-06-01,73.24,groceries
281 | 2022-06-01,19.85,groceries
282 | 2022-06-01,47.97,housing
283 |
--------------------------------------------------------------------------------
/part1/environment/README.md:
--------------------------------------------------------------------------------
1 | # Environment Setup
2 |
3 | ## Installation with Anaconda/Miniconda
4 |
5 | Run the two commands from the root directory.
6 |
7 | ```shell
8 | conda env create -f ./environment/conda.yaml
9 | conda activate dash-app-tutorial
10 | ```
11 |
12 | ## Installation with Pip
13 |
14 | **Note:** Python >= 3.9 is required to use the [modern style](https://peps.python.org/pep-0585/) of type annotations.
15 |
16 | Run the command from the root directory.
17 |
18 | ```shell
19 | python -m pip install -r ./environment/requirements.txt
20 | ```
21 |
--------------------------------------------------------------------------------
/part1/environment/conda.yaml:
--------------------------------------------------------------------------------
1 | name: dash-app-tutorial
2 | dependencies:
3 | - python>=3.9
4 | - pip
5 | - pip:
6 | - -r requirements.txt
7 |
--------------------------------------------------------------------------------
/part1/environment/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | black
2 | pylint
--------------------------------------------------------------------------------
/part1/environment/requirements.txt:
--------------------------------------------------------------------------------
1 | Babel
2 | dash
3 | dash-bootstrap-components
4 | pandas
5 | pandas-stubs
6 | plotly
7 | python-i18n[YAML]
--------------------------------------------------------------------------------
/part1/main.py:
--------------------------------------------------------------------------------
1 | from dash import Dash
2 | from dash_bootstrap_components.themes import BOOTSTRAP
3 |
4 | from src.components.layout import create_layout
5 |
6 |
7 | def main() -> None:
8 | app = Dash(external_stylesheets=[BOOTSTRAP])
9 | app.title = "Medal dashboard"
10 | app.layout = create_layout(app)
11 | app.run()
12 |
13 |
14 | if __name__ == "__main__":
15 | main()
16 |
--------------------------------------------------------------------------------
/part1/src/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArjanCodes/2022-dash/a0e56e00c98900485b0c75e5d5ce99b36ac5a2d2/part1/src/__init__.py
--------------------------------------------------------------------------------
/part1/src/components/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArjanCodes/2022-dash/a0e56e00c98900485b0c75e5d5ce99b36ac5a2d2/part1/src/components/__init__.py
--------------------------------------------------------------------------------
/part1/src/components/bar_chart.py:
--------------------------------------------------------------------------------
1 | import plotly.express as px
2 | from dash import Dash, dcc, html
3 | from dash.dependencies import Input, Output
4 |
5 | from . import ids
6 |
7 | MEDAL_DATA = px.data.medals_long()
8 |
9 |
10 | def render(app: Dash) -> html.Div:
11 | @app.callback(
12 | Output(ids.BAR_CHART, "children"),
13 | [
14 | Input(ids.NATION_DROPDOWN, "value"),
15 | ],
16 | )
17 | def update_bar_chart(nations: list[str]) -> html.Div:
18 | filtered_data = MEDAL_DATA.query("nation in @nations")
19 |
20 | if filtered_data.shape[0] == 0:
21 | return html.Div("No data selected.", id=ids.BAR_CHART)
22 |
23 | fig = px.bar(filtered_data, x="medal", y="count", color="nation", text="nation")
24 |
25 | return html.Div(dcc.Graph(figure=fig), id=ids.BAR_CHART)
26 |
27 | return html.Div(id=ids.BAR_CHART)
28 |
--------------------------------------------------------------------------------
/part1/src/components/ids.py:
--------------------------------------------------------------------------------
1 | BAR_CHART = "bar-chart"
2 |
3 | NATION_DROPDOWN = "nation-dropdown"
4 | SELECT_ALL_NATIONS_BUTTON = "select-all-nations-button"
5 |
--------------------------------------------------------------------------------
/part1/src/components/layout.py:
--------------------------------------------------------------------------------
1 | from dash import Dash, html
2 |
3 | from . import bar_chart, nation_dropdown
4 |
5 |
6 | def create_layout(app: Dash) -> html.Div:
7 | return html.Div(
8 | className="app-div",
9 | children=[
10 | html.H1(app.title),
11 | html.Hr(),
12 | html.Div(
13 | className="dropdown-container",
14 | children=[
15 | nation_dropdown.render(app),
16 | ],
17 | ),
18 | bar_chart.render(app),
19 | ],
20 | )
21 |
--------------------------------------------------------------------------------
/part1/src/components/nation_dropdown.py:
--------------------------------------------------------------------------------
1 | from dash import Dash, dcc, html
2 | from dash.dependencies import Input, Output
3 |
4 | from . import ids
5 |
6 |
7 | def render(app: Dash) -> html.Div:
8 | all_nations = ["South Korea", "China", "Canada"]
9 |
10 | @app.callback(
11 | Output(ids.NATION_DROPDOWN, "value"),
12 | Input(ids.SELECT_ALL_NATIONS_BUTTON, "n_clicks"),
13 | )
14 | def select_all_nations(_: int) -> list[str]:
15 | return all_nations
16 |
17 | return html.Div(
18 | children=[
19 | html.H6("Nation"),
20 | dcc.Dropdown(
21 | id=ids.NATION_DROPDOWN,
22 | options=[{"label": year, "value": year} for year in all_nations],
23 | value=all_nations,
24 | multi=True,
25 | ),
26 | html.Button(
27 | className="dropdown-button",
28 | children=["Select All"],
29 | id=ids.SELECT_ALL_NATIONS_BUTTON,
30 | n_clicks=0,
31 | ),
32 | ]
33 | )
34 |
--------------------------------------------------------------------------------
/part2/README.md:
--------------------------------------------------------------------------------
1 | # Dash App Tutorial
2 |
3 | ## Running the app
4 |
5 | Run the following commands and open the local host web address chosen by Dash.
6 |
7 | ```shell
8 | python ./main.py
9 | ```
10 |
11 | An example of the expected terminal messages are shown below:
12 |
13 | ```shell
14 | Dash is running on http://127.0.0.1:8050/
15 |
16 | * Serving Flask app 'app' (lazy loading)
17 | * Environment: production
18 | WARNING: This is a development server. Do not use it in a production deployment.
19 | Use a production WSGI server instead.
20 | * Debug mode: on
21 | ```
22 |
--------------------------------------------------------------------------------
/part2/assets/style.css:
--------------------------------------------------------------------------------
1 | hr {
2 | border-top: 2px solid gray;
3 | }
4 |
5 | .dropdown-container {
6 | display: grid;
7 | grid-template-columns: repeat(3, 1fr);
8 | gap: 10px;
9 | }
10 |
11 | .dropdown-button {
12 | width: 100%;
13 | margin-top: 5px;
14 | }
15 |
16 | .tabs-group {
17 | padding-top: 20px;
18 | }
19 |
20 | .app-div {
21 | padding: 10px;
22 | }
23 |
24 | .data-table-div {
25 | margin: auto;
26 | padding: 10px;
27 | }
28 |
29 | .budget-label {
30 | font-size: 20px;
31 | font-weight: bold;
32 | }
33 |
34 | .budget-text {
35 | font-size: 20px;
36 | }
37 |
38 | .budget-text-value {
39 | font-size: 20px;
40 | font-style: italic;
41 | }
42 |
43 | .budget-progress-bar {
44 | height: 20px;
45 | }
46 |
47 | .budget-tooltip {
48 | font-size: 18px;
49 | }
50 |
51 | .checklist {
52 | text-align: center;
53 | padding-top: 10px;
54 | padding-bottom: 20px;
55 | }
56 |
57 | @media(max-width: 800px) {
58 | .dropdown-container {
59 | grid-template-columns: 1fr;
60 | }
61 | }
--------------------------------------------------------------------------------
/part2/data/transactions.csv:
--------------------------------------------------------------------------------
1 | date,amount,category
2 | 2020-01-01,89.92,groceries
3 | 2020-01-01,48.74,entertainment
4 | 2020-01-01,39.08,groceries
5 | 2020-01-01,48.36,groceries
6 | 2020-01-01,77.36,groceries
7 | 2020-01-01,73.46,groceries
8 | 2020-01-01,57.16,groceries
9 | 2020-01-01,75.94,groceries
10 | 2020-02-01,76.29,healthcare
11 | 2020-02-01,16.09,entertainment
12 | 2020-02-01,82.62,groceries
13 | 2020-02-01,21.61,utilities
14 | 2020-02-01,43.55,entertainment
15 | 2020-02-01,28.57,car
16 | 2020-02-01,86.28,groceries
17 | 2020-02-01,43.51,housing
18 | 2020-02-01,7.93,car
19 | 2020-03-01,51.83,groceries
20 | 2020-03-01,48.8,travel
21 | 2020-03-01,30.33,groceries
22 | 2020-03-01,85.14,groceries
23 | 2020-03-01,96.06,groceries
24 | 2020-03-01,66.16,housing
25 | 2020-03-01,53.73,groceries
26 | 2020-03-01,64.25,healthcare
27 | 2020-03-01,94.17,groceries
28 | 2020-04-01,65.61,housing
29 | 2020-04-01,11.49,travel
30 | 2020-04-01,88.06,groceries
31 | 2020-04-01,58.63,groceries
32 | 2020-04-01,28.14,groceries
33 | 2020-04-01,32.49,utilities
34 | 2020-04-01,6.55,groceries
35 | 2020-04-01,94.11,groceries
36 | 2020-05-01,1.19,groceries
37 | 2020-05-01,56.66,groceries
38 | 2020-05-01,49.05,entertainment
39 | 2020-05-01,41.46,healthcare
40 | 2020-05-01,71.48,entertainment
41 | 2020-05-01,32.84,groceries
42 | 2020-05-01,78.47,entertainment
43 | 2020-05-01,29.33,groceries
44 | 2020-06-01,87.67,groceries
45 | 2020-06-01,44.3,housing
46 | 2020-06-01,56.0,entertainment
47 | 2020-06-01,66.25,travel
48 | 2020-06-01,97.5,healthcare
49 | 2020-06-01,29.02,groceries
50 | 2020-06-01,97.86,groceries
51 | 2020-07-01,47.77,housing
52 | 2020-07-01,80.89,groceries
53 | 2020-07-01,21.09,utilities
54 | 2020-07-01,4.62,car
55 | 2020-07-01,96.13,utilities
56 | 2020-07-01,12.53,groceries
57 | 2020-07-01,35.77,entertainment
58 | 2020-08-01,82.73,healthcare
59 | 2020-08-01,47.77,groceries
60 | 2020-08-01,74.13,groceries
61 | 2020-08-01,38.33,entertainment
62 | 2020-08-01,65.55,housing
63 | 2020-08-01,6.29,groceries
64 | 2020-08-01,25.23,groceries
65 | 2020-09-01,49.51,travel
66 | 2020-09-01,17.92,entertainment
67 | 2020-09-01,74.69,travel
68 | 2020-09-01,89.4,groceries
69 | 2020-09-01,55.0,travel
70 | 2020-09-01,81.93,utilities
71 | 2020-09-01,57.69,healthcare
72 | 2020-09-01,22.22,entertainment
73 | 2020-09-01,13.22,entertainment
74 | 2020-09-01,77.76,groceries
75 | 2020-09-01,13.22,groceries
76 | 2020-10-01,57.58,groceries
77 | 2020-10-01,4.0,car
78 | 2020-10-01,20.48,utilities
79 | 2020-10-01,15.74,utilities
80 | 2020-10-01,59.31,entertainment
81 | 2020-10-01,15.31,groceries
82 | 2020-10-01,96.55,groceries
83 | 2020-10-01,91.65,entertainment
84 | 2020-10-01,50.04,groceries
85 | 2020-10-01,80.78,groceries
86 | 2020-11-01,71.81,groceries
87 | 2020-11-01,24.68,housing
88 | 2020-11-01,69.69,healthcare
89 | 2020-11-01,56.34,groceries
90 | 2020-12-01,66.58,entertainment
91 | 2020-12-01,76.24,utilities
92 | 2020-12-01,81.17,groceries
93 | 2020-12-01,47.01,car
94 | 2020-12-01,71.57,groceries
95 | 2020-12-01,73.81,utilities
96 | 2020-12-01,9.22,healthcare
97 | 2020-12-01,32.22,travel
98 | 2020-12-01,5.72,entertainment
99 | 2020-12-01,73.53,groceries
100 | 2021-01-01,70.84,entertainment
101 | 2021-01-01,99.42,entertainment
102 | 2021-01-01,37.04,healthcare
103 | 2021-01-01,90.98,healthcare
104 | 2021-01-01,58.67,healthcare
105 | 2021-01-01,92.68,groceries
106 | 2021-01-01,65.82,groceries
107 | 2021-01-01,99.58,groceries
108 | 2021-01-01,16.65,healthcare
109 | 2021-01-01,10.93,groceries
110 | 2021-02-01,33.45,groceries
111 | 2021-02-01,70.84,travel
112 | 2021-02-01,34.73,entertainment
113 | 2021-02-01,46.42,groceries
114 | 2021-02-01,91.4,healthcare
115 | 2021-02-01,71.29,travel
116 | 2021-02-01,21.49,entertainment
117 | 2021-02-01,18.48,groceries
118 | 2021-02-01,55.53,healthcare
119 | 2021-02-01,77.15,groceries
120 | 2021-03-01,43.56,groceries
121 | 2021-03-01,83.79,healthcare
122 | 2021-03-01,84.38,travel
123 | 2021-03-01,24.06,groceries
124 | 2021-03-01,56.91,groceries
125 | 2021-03-01,9.95,travel
126 | 2021-03-01,17.04,housing
127 | 2021-03-01,65.6,entertainment
128 | 2021-03-01,48.31,housing
129 | 2021-03-01,53.55,healthcare
130 | 2021-04-01,85.99,car
131 | 2021-04-01,59.48,travel
132 | 2021-04-01,1.1,travel
133 | 2021-04-01,54.48,groceries
134 | 2021-04-01,14.25,car
135 | 2021-04-01,24.01,groceries
136 | 2021-04-01,66.32,healthcare
137 | 2021-04-01,38.34,healthcare
138 | 2021-04-01,62.12,groceries
139 | 2021-04-01,14.14,housing
140 | 2021-04-01,64.07,groceries
141 | 2021-04-01,97.5,housing
142 | 2021-04-01,97.26,entertainment
143 | 2021-04-01,90.99,entertainment
144 | 2021-05-01,64.94,car
145 | 2021-05-01,36.64,groceries
146 | 2021-05-01,70.0,groceries
147 | 2021-05-01,90.62,groceries
148 | 2021-05-01,92.65,groceries
149 | 2021-05-01,77.18,groceries
150 | 2021-05-01,57.91,groceries
151 | 2021-05-01,43.15,housing
152 | 2021-06-01,83.8,groceries
153 | 2021-06-01,49.62,entertainment
154 | 2021-06-01,71.06,groceries
155 | 2021-06-01,33.25,groceries
156 | 2021-06-01,53.4,groceries
157 | 2021-06-01,4.61,entertainment
158 | 2021-06-01,2.51,utilities
159 | 2021-06-01,56.95,entertainment
160 | 2021-06-01,82.12,travel
161 | 2021-06-01,29.88,entertainment
162 | 2021-06-01,1.36,utilities
163 | 2021-07-01,40.01,housing
164 | 2021-07-01,47.98,groceries
165 | 2021-07-01,45.75,groceries
166 | 2021-07-01,95.4,groceries
167 | 2021-07-01,90.35,groceries
168 | 2021-07-01,82.36,groceries
169 | 2021-07-01,92.92,car
170 | 2021-07-01,16.85,entertainment
171 | 2021-07-01,82.77,groceries
172 | 2021-07-01,63.85,utilities
173 | 2021-07-01,10.82,entertainment
174 | 2021-08-01,42.11,groceries
175 | 2021-08-01,10.76,groceries
176 | 2021-08-01,49.48,entertainment
177 | 2021-08-01,53.11,groceries
178 | 2021-08-01,51.99,groceries
179 | 2021-08-01,75.16,utilities
180 | 2021-08-01,53.6,groceries
181 | 2021-08-01,37.27,groceries
182 | 2021-08-01,11.23,groceries
183 | 2021-09-01,45.43,groceries
184 | 2021-09-01,73.96,groceries
185 | 2021-09-01,45.1,groceries
186 | 2021-09-01,25.35,groceries
187 | 2021-09-01,68.04,groceries
188 | 2021-09-01,62.09,groceries
189 | 2021-09-01,66.98,utilities
190 | 2021-09-01,32.28,groceries
191 | 2021-09-01,19.8,entertainment
192 | 2021-09-01,69.23,groceries
193 | 2021-10-01,56.28,healthcare
194 | 2021-10-01,2.16,entertainment
195 | 2021-10-01,59.04,groceries
196 | 2021-10-01,89.95,groceries
197 | 2021-10-01,35.4,groceries
198 | 2021-10-01,75.07,entertainment
199 | 2021-10-01,34.26,groceries
200 | 2021-11-01,95.83,groceries
201 | 2021-11-01,9.02,utilities
202 | 2021-11-01,85.69,healthcare
203 | 2021-11-01,24.64,healthcare
204 | 2021-11-01,49.18,healthcare
205 | 2021-11-01,53.08,entertainment
206 | 2021-11-01,29.73,travel
207 | 2021-11-01,17.31,groceries
208 | 2021-11-01,76.38,groceries
209 | 2021-11-01,89.54,groceries
210 | 2021-12-01,53.16,groceries
211 | 2021-12-01,53.25,entertainment
212 | 2021-12-01,91.38,car
213 | 2021-12-01,77.33,entertainment
214 | 2021-12-01,85.64,groceries
215 | 2021-12-01,61.59,entertainment
216 | 2021-12-01,43.79,car
217 | 2021-12-01,44.01,travel
218 | 2021-12-01,13.41,healthcare
219 | 2022-01-01,96.68,housing
220 | 2022-01-01,18.45,groceries
221 | 2022-01-01,78.89,groceries
222 | 2022-01-01,6.87,healthcare
223 | 2022-01-01,56.18,entertainment
224 | 2022-01-01,63.06,groceries
225 | 2022-01-01,15.97,groceries
226 | 2022-01-01,50.25,groceries
227 | 2022-01-01,58.76,groceries
228 | 2022-01-01,24.09,groceries
229 | 2022-01-01,31.04,groceries
230 | 2022-01-01,70.04,groceries
231 | 2022-01-01,25.06,groceries
232 | 2022-01-01,61.61,travel
233 | 2022-01-01,39.21,groceries
234 | 2022-01-01,11.46,groceries
235 | 2022-02-01,14.8,housing
236 | 2022-02-01,81.78,healthcare
237 | 2022-02-01,12.49,entertainment
238 | 2022-02-01,68.73,utilities
239 | 2022-02-01,30.66,groceries
240 | 2022-02-01,56.6,groceries
241 | 2022-02-01,73.0,entertainment
242 | 2022-02-01,7.76,travel
243 | 2022-03-01,22.03,groceries
244 | 2022-03-01,74.89,groceries
245 | 2022-03-01,27.95,groceries
246 | 2022-03-01,97.88,travel
247 | 2022-03-01,29.95,entertainment
248 | 2022-03-01,33.46,entertainment
249 | 2022-03-01,11.64,entertainment
250 | 2022-03-01,28.7,groceries
251 | 2022-03-01,84.11,groceries
252 | 2022-03-01,65.94,healthcare
253 | 2022-03-01,68.89,groceries
254 | 2022-03-01,99.26,car
255 | 2022-03-01,19.47,car
256 | 2022-03-01,79.93,entertainment
257 | 2022-04-01,81.24,groceries
258 | 2022-04-01,32.05,utilities
259 | 2022-04-01,74.43,utilities
260 | 2022-04-01,92.54,entertainment
261 | 2022-04-01,67.87,entertainment
262 | 2022-04-01,49.54,groceries
263 | 2022-04-01,72.31,entertainment
264 | 2022-04-01,74.85,groceries
265 | 2022-04-01,56.05,groceries
266 | 2022-04-01,69.44,travel
267 | 2022-04-01,85.56,groceries
268 | 2022-05-01,14.99,healthcare
269 | 2022-05-01,29.46,groceries
270 | 2022-05-01,17.41,groceries
271 | 2022-05-01,83.34,groceries
272 | 2022-05-01,40.07,groceries
273 | 2022-05-01,78.19,entertainment
274 | 2022-05-01,32.96,housing
275 | 2022-05-01,89.92,entertainment
276 | 2022-06-01,86.82,groceries
277 | 2022-06-01,69.78,entertainment
278 | 2022-06-01,66.43,groceries
279 | 2022-06-01,58.21,groceries
280 | 2022-06-01,73.24,groceries
281 | 2022-06-01,19.85,groceries
282 | 2022-06-01,47.97,housing
283 |
--------------------------------------------------------------------------------
/part2/environment/README.md:
--------------------------------------------------------------------------------
1 | # Environment Setup
2 |
3 | ## Installation with Anaconda/Miniconda
4 |
5 | Run the two commands from the root directory.
6 |
7 | ```shell
8 | conda env create -f ./environment/conda.yaml
9 | conda activate dash-app-tutorial
10 | ```
11 |
12 | ## Installation with Pip
13 |
14 | **Note:** Python >= 3.9 is required to use the [modern style](https://peps.python.org/pep-0585/) of type annotations.
15 |
16 | Run the command from the root directory.
17 |
18 | ```shell
19 | python -m pip install -r ./environment/requirements.txt
20 | ```
21 |
--------------------------------------------------------------------------------
/part2/environment/conda.yaml:
--------------------------------------------------------------------------------
1 | name: dash-app-tutorial
2 | dependencies:
3 | - python>=3.9
4 | - pip
5 | - pip:
6 | - -r requirements.txt
7 |
--------------------------------------------------------------------------------
/part2/environment/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | black
2 | pylint
--------------------------------------------------------------------------------
/part2/environment/requirements.txt:
--------------------------------------------------------------------------------
1 | Babel
2 | dash
3 | dash-bootstrap-components
4 | pandas
5 | pandas-stubs
6 | plotly
7 | python-i18n[YAML]
--------------------------------------------------------------------------------
/part2/main.py:
--------------------------------------------------------------------------------
1 | from dash import Dash
2 | from dash_bootstrap_components.themes import BOOTSTRAP
3 |
4 | from src.components.layout import create_layout
5 | from src.data.loader import load_transaction_data
6 |
7 | DATA_PATH = "./data/transactions.csv"
8 |
9 |
10 | def main() -> None:
11 |
12 | # load the data and create the data manager
13 | data = load_transaction_data(DATA_PATH)
14 |
15 | app = Dash(external_stylesheets=[BOOTSTRAP])
16 | app.title = "Financial Dashboard"
17 | app.layout = create_layout(app, data)
18 | app.run()
19 |
20 |
21 | if __name__ == "__main__":
22 | main()
23 |
--------------------------------------------------------------------------------
/part2/src/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArjanCodes/2022-dash/a0e56e00c98900485b0c75e5d5ce99b36ac5a2d2/part2/src/__init__.py
--------------------------------------------------------------------------------
/part2/src/components/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArjanCodes/2022-dash/a0e56e00c98900485b0c75e5d5ce99b36ac5a2d2/part2/src/components/__init__.py
--------------------------------------------------------------------------------
/part2/src/components/bar_chart.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | import plotly.express as px
3 | from dash import Dash, dcc, html
4 | from dash.dependencies import Input, Output
5 |
6 | from ..data.loader import DataSchema
7 | from . import ids
8 |
9 |
10 | def render(app: Dash, data: pd.DataFrame) -> html.Div:
11 | @app.callback(
12 | Output(ids.BAR_CHART, "children"),
13 | [
14 | Input(ids.YEAR_DROPDOWN, "value"),
15 | Input(ids.MONTH_DROPDOWN, "value"),
16 | Input(ids.CATEGORY_DROPDOWN, "value"),
17 | ],
18 | )
19 | def update_bar_chart(
20 | years: list[str], months: list[str], categories: list[str]
21 | ) -> html.Div:
22 | filtered_data = data.query(
23 | "year in @years and month in @months and category in @categories"
24 | )
25 |
26 | if filtered_data.shape[0] == 0:
27 | return html.Div("No data selected.", id=ids.BAR_CHART)
28 |
29 | def create_pivot_table() -> pd.DataFrame:
30 | pt = filtered_data.pivot_table(
31 | values=DataSchema.AMOUNT,
32 | index=[DataSchema.CATEGORY],
33 | aggfunc="sum",
34 | fill_value=0,
35 | dropna=False,
36 | )
37 | return pt.reset_index().sort_values(DataSchema.AMOUNT, ascending=False)
38 |
39 | fig = px.bar(
40 | create_pivot_table(),
41 | x=DataSchema.CATEGORY,
42 | y=DataSchema.AMOUNT,
43 | color=DataSchema.CATEGORY,
44 | )
45 |
46 | return html.Div(dcc.Graph(figure=fig), id=ids.BAR_CHART)
47 |
48 | return html.Div(id=ids.BAR_CHART)
49 |
--------------------------------------------------------------------------------
/part2/src/components/category_dropdown.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | from dash import Dash, dcc, html
3 | from dash.dependencies import Input, Output
4 |
5 | from ..data.loader import DataSchema
6 | from . import ids
7 |
8 |
9 | def render(app: Dash, data: pd.DataFrame) -> html.Div:
10 | all_categories: list[str] = data[DataSchema.CATEGORY].tolist()
11 | unique_categories: list[str] = sorted(set(all_categories))
12 |
13 | @app.callback(
14 | Output(ids.CATEGORY_DROPDOWN, "value"),
15 | [
16 | Input(ids.YEAR_DROPDOWN, "value"),
17 | Input(ids.MONTH_DROPDOWN, "value"),
18 | Input(ids.SELECT_ALL_CATEGORIES_BUTTON, "n_clicks"),
19 | ],
20 | )
21 | def select_all_categories(years: list[str], months: list[str], _: int) -> list[str]:
22 | filtered_data = data.query("year in @years and month in @months")
23 | return sorted(set(filtered_data[DataSchema.CATEGORY].tolist()))
24 |
25 | return html.Div(
26 | children=[
27 | html.H6("Category"),
28 | dcc.Dropdown(
29 | id=ids.CATEGORY_DROPDOWN,
30 | options=[
31 | {"label": category, "value": category}
32 | for category in unique_categories
33 | ],
34 | value=unique_categories,
35 | multi=True,
36 | placeholder="Select",
37 | ),
38 | html.Button(
39 | className="dropdown-button",
40 | children=["Select All"],
41 | id=ids.SELECT_ALL_CATEGORIES_BUTTON,
42 | n_clicks=0,
43 | ),
44 | ],
45 | )
46 |
--------------------------------------------------------------------------------
/part2/src/components/ids.py:
--------------------------------------------------------------------------------
1 | BAR_CHART = "bar-chart"
2 | PIE_CHART = "pie-chart"
3 |
4 | SELECT_ALL_CATEGORIES_BUTTON = "select-all-categories-button"
5 | CATEGORY_DROPDOWN = "category-dropdown"
6 |
7 | SELECT_ALL_MONTHS_BUTTON = "select-all-months-button"
8 | MONTH_DROPDOWN = "month-dropdown"
9 |
10 | YEAR_DROPDOWN = "year-dropdown"
11 | SELECT_ALL_YEARS_BUTTON = "select-all-years-button"
12 |
--------------------------------------------------------------------------------
/part2/src/components/layout.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | from dash import Dash, html
3 | from src.components import (
4 | bar_chart,
5 | category_dropdown,
6 | month_dropdown,
7 | pie_chart,
8 | year_dropdown,
9 | )
10 |
11 |
12 | def create_layout(app: Dash, data: pd.DataFrame) -> html.Div:
13 | return html.Div(
14 | className="app-div",
15 | children=[
16 | html.H1(app.title),
17 | html.Hr(),
18 | html.Div(
19 | className="dropdown-container",
20 | children=[
21 | year_dropdown.render(app, data),
22 | month_dropdown.render(app, data),
23 | category_dropdown.render(app, data),
24 | ],
25 | ),
26 | bar_chart.render(app, data),
27 | pie_chart.render(app, data),
28 | ],
29 | )
30 |
--------------------------------------------------------------------------------
/part2/src/components/month_dropdown.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | from dash import Dash, dcc, html
3 | from dash.dependencies import Input, Output
4 |
5 | from ..data.loader import DataSchema
6 | from . import ids
7 |
8 |
9 | def render(app: Dash, data: pd.DataFrame) -> html.Div:
10 | all_months: list[str] = data[DataSchema.MONTH].tolist()
11 | unique_months = sorted(set(all_months))
12 |
13 | @app.callback(
14 | Output(ids.MONTH_DROPDOWN, "value"),
15 | [
16 | Input(ids.YEAR_DROPDOWN, "value"),
17 | Input(ids.SELECT_ALL_MONTHS_BUTTON, "n_clicks"),
18 | ],
19 | )
20 | def select_all_months(years: list[str], _: int) -> list[str]:
21 | filtered_data = data.query("year in @years")
22 | return sorted(set(filtered_data[DataSchema.MONTH].tolist()))
23 |
24 | return html.Div(
25 | children=[
26 | html.H6("Month"),
27 | dcc.Dropdown(
28 | id=ids.MONTH_DROPDOWN,
29 | options=[{"label": month, "value": month} for month in unique_months],
30 | value=unique_months,
31 | multi=True,
32 | ),
33 | html.Button(
34 | className="dropdown-button",
35 | children=["Select All"],
36 | id=ids.SELECT_ALL_MONTHS_BUTTON,
37 | n_clicks=0,
38 | ),
39 | ]
40 | )
41 |
--------------------------------------------------------------------------------
/part2/src/components/pie_chart.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | import plotly.graph_objects as go
3 | from dash import Dash, dcc, html
4 | from dash.dependencies import Input, Output
5 |
6 | from ..data.loader import DataSchema
7 | from . import ids
8 |
9 |
10 | def render(app: Dash, data: pd.DataFrame) -> html.Div:
11 | @app.callback(
12 | Output(ids.PIE_CHART, "children"),
13 | [
14 | Input(ids.YEAR_DROPDOWN, "value"),
15 | Input(ids.MONTH_DROPDOWN, "value"),
16 | Input(ids.CATEGORY_DROPDOWN, "value"),
17 | ],
18 | )
19 | def update_pie_chart(
20 | years: list[str], months: list[str], categories: list[str]
21 | ) -> html.Div:
22 | filtered_data = data.query(
23 | "year in @years and month in @months and category in @categories"
24 | )
25 |
26 | if filtered_data.shape[0] == 0:
27 | return html.Div("No data selected.", id=ids.PIE_CHART)
28 |
29 | pie = go.Pie(
30 | labels=filtered_data[DataSchema.CATEGORY].tolist(),
31 | values=filtered_data[DataSchema.AMOUNT].tolist(),
32 | hole=0.5,
33 | )
34 |
35 | fig = go.Figure(data=[pie])
36 | fig.update_layout(margin={"t": 40, "b": 0, "l": 0, "r": 0})
37 | fig.update_traces(hovertemplate="%{label}
$%{value:.2f}")
38 |
39 | return html.Div(dcc.Graph(figure=fig), id=ids.PIE_CHART)
40 |
41 | return html.Div(id=ids.PIE_CHART)
42 |
--------------------------------------------------------------------------------
/part2/src/components/year_dropdown.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | from dash import Dash, dcc, html
3 | from dash.dependencies import Input, Output
4 |
5 | from ..data.loader import DataSchema
6 | from . import ids
7 |
8 |
9 | def render(app: Dash, data: pd.DataFrame) -> html.Div:
10 | all_years: list[str] = data[DataSchema.YEAR].tolist()
11 | unique_years = sorted(set(all_years), key=int)
12 |
13 | @app.callback(
14 | Output(ids.YEAR_DROPDOWN, "value"),
15 | Input(ids.SELECT_ALL_YEARS_BUTTON, "n_clicks"),
16 | )
17 | def select_all_years(_: int) -> list[str]:
18 | return unique_years
19 |
20 | return html.Div(
21 | children=[
22 | html.H6("Year"),
23 | dcc.Dropdown(
24 | id=ids.YEAR_DROPDOWN,
25 | options=[{"label": year, "value": year} for year in unique_years],
26 | value=unique_years,
27 | multi=True,
28 | ),
29 | html.Button(
30 | className="dropdown-button",
31 | children=["Select All"],
32 | id=ids.SELECT_ALL_YEARS_BUTTON,
33 | n_clicks=0,
34 | ),
35 | ]
36 | )
37 |
--------------------------------------------------------------------------------
/part2/src/data/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArjanCodes/2022-dash/a0e56e00c98900485b0c75e5d5ce99b36ac5a2d2/part2/src/data/__init__.py
--------------------------------------------------------------------------------
/part2/src/data/loader.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 |
3 |
4 | class DataSchema:
5 | AMOUNT = "amount"
6 | CATEGORY = "category"
7 | DATE = "date"
8 | MONTH = "month"
9 | YEAR = "year"
10 |
11 |
12 | def load_transaction_data(path: str) -> pd.DataFrame:
13 | # load the data from the CSV file
14 | data = pd.read_csv(
15 | path,
16 | dtype={
17 | DataSchema.AMOUNT: float,
18 | DataSchema.CATEGORY: str,
19 | DataSchema.DATE: str,
20 | },
21 | parse_dates=[DataSchema.DATE],
22 | )
23 | data[DataSchema.YEAR] = data[DataSchema.DATE].dt.year.astype(str)
24 | data[DataSchema.MONTH] = data[DataSchema.DATE].dt.month.astype(str)
25 | return data
26 |
--------------------------------------------------------------------------------
/part3/README.md:
--------------------------------------------------------------------------------
1 | # Dash App Tutorial
2 |
3 | ## Running the app
4 |
5 | Run the following commands and open the local host web address chosen by Dash.
6 |
7 | ```shell
8 | python ./main.py
9 | ```
10 |
11 | An example of the expected terminal messages are shown below:
12 |
13 | ```shell
14 | Dash is running on http://127.0.0.1:8050/
15 |
16 | * Serving Flask app 'app' (lazy loading)
17 | * Environment: production
18 | WARNING: This is a development server. Do not use it in a production deployment.
19 | Use a production WSGI server instead.
20 | * Debug mode: on
21 | ```
22 |
--------------------------------------------------------------------------------
/part3/assets/style.css:
--------------------------------------------------------------------------------
1 | hr {
2 | border-top: 2px solid gray;
3 | }
4 |
5 | .dropdown-container {
6 | display: grid;
7 | grid-template-columns: repeat(3, 1fr);
8 | gap: 10px;
9 | }
10 |
11 | .dropdown-button {
12 | width: 100%;
13 | margin-top: 5px;
14 | }
15 |
16 | .tabs-group {
17 | padding-top: 20px;
18 | }
19 |
20 | .app-div {
21 | padding: 10px;
22 | }
23 |
24 | .data-table-div {
25 | margin: auto;
26 | padding: 10px;
27 | }
28 |
29 | .budget-label {
30 | font-size: 20px;
31 | font-weight: bold;
32 | }
33 |
34 | .budget-text {
35 | font-size: 20px;
36 | }
37 |
38 | .budget-text-value {
39 | font-size: 20px;
40 | font-style: italic;
41 | }
42 |
43 | .budget-progress-bar {
44 | height: 20px;
45 | }
46 |
47 | .budget-tooltip {
48 | font-size: 18px;
49 | }
50 |
51 | .checklist {
52 | text-align: center;
53 | padding-top: 10px;
54 | padding-bottom: 20px;
55 | }
56 |
57 | @media(max-width: 800px) {
58 | .dropdown-container {
59 | grid-template-columns: 1fr;
60 | }
61 | }
--------------------------------------------------------------------------------
/part3/data/transactions.csv:
--------------------------------------------------------------------------------
1 | date,amount,category
2 | 2020-01-01,89.92,groceries
3 | 2020-01-01,48.74,entertainment
4 | 2020-01-01,39.08,groceries
5 | 2020-01-01,48.36,groceries
6 | 2020-01-01,77.36,groceries
7 | 2020-01-01,73.46,groceries
8 | 2020-01-01,57.16,groceries
9 | 2020-01-01,75.94,groceries
10 | 2020-02-01,76.29,healthcare
11 | 2020-02-01,16.09,entertainment
12 | 2020-02-01,82.62,groceries
13 | 2020-02-01,21.61,utilities
14 | 2020-02-01,43.55,entertainment
15 | 2020-02-01,28.57,car
16 | 2020-02-01,86.28,groceries
17 | 2020-02-01,43.51,housing
18 | 2020-02-01,7.93,car
19 | 2020-03-01,51.83,groceries
20 | 2020-03-01,48.8,travel
21 | 2020-03-01,30.33,groceries
22 | 2020-03-01,85.14,groceries
23 | 2020-03-01,96.06,groceries
24 | 2020-03-01,66.16,housing
25 | 2020-03-01,53.73,groceries
26 | 2020-03-01,64.25,healthcare
27 | 2020-03-01,94.17,groceries
28 | 2020-04-01,65.61,housing
29 | 2020-04-01,11.49,travel
30 | 2020-04-01,88.06,groceries
31 | 2020-04-01,58.63,groceries
32 | 2020-04-01,28.14,groceries
33 | 2020-04-01,32.49,utilities
34 | 2020-04-01,6.55,groceries
35 | 2020-04-01,94.11,groceries
36 | 2020-05-01,1.19,groceries
37 | 2020-05-01,56.66,groceries
38 | 2020-05-01,49.05,entertainment
39 | 2020-05-01,41.46,healthcare
40 | 2020-05-01,71.48,entertainment
41 | 2020-05-01,32.84,groceries
42 | 2020-05-01,78.47,entertainment
43 | 2020-05-01,29.33,groceries
44 | 2020-06-01,87.67,groceries
45 | 2020-06-01,44.3,housing
46 | 2020-06-01,56.0,entertainment
47 | 2020-06-01,66.25,travel
48 | 2020-06-01,97.5,healthcare
49 | 2020-06-01,29.02,groceries
50 | 2020-06-01,97.86,groceries
51 | 2020-07-01,47.77,housing
52 | 2020-07-01,80.89,groceries
53 | 2020-07-01,21.09,utilities
54 | 2020-07-01,4.62,car
55 | 2020-07-01,96.13,utilities
56 | 2020-07-01,12.53,groceries
57 | 2020-07-01,35.77,entertainment
58 | 2020-08-01,82.73,healthcare
59 | 2020-08-01,47.77,groceries
60 | 2020-08-01,74.13,groceries
61 | 2020-08-01,38.33,entertainment
62 | 2020-08-01,65.55,housing
63 | 2020-08-01,6.29,groceries
64 | 2020-08-01,25.23,groceries
65 | 2020-09-01,49.51,travel
66 | 2020-09-01,17.92,entertainment
67 | 2020-09-01,74.69,travel
68 | 2020-09-01,89.4,groceries
69 | 2020-09-01,55.0,travel
70 | 2020-09-01,81.93,utilities
71 | 2020-09-01,57.69,healthcare
72 | 2020-09-01,22.22,entertainment
73 | 2020-09-01,13.22,entertainment
74 | 2020-09-01,77.76,groceries
75 | 2020-09-01,13.22,groceries
76 | 2020-10-01,57.58,groceries
77 | 2020-10-01,4.0,car
78 | 2020-10-01,20.48,utilities
79 | 2020-10-01,15.74,utilities
80 | 2020-10-01,59.31,entertainment
81 | 2020-10-01,15.31,groceries
82 | 2020-10-01,96.55,groceries
83 | 2020-10-01,91.65,entertainment
84 | 2020-10-01,50.04,groceries
85 | 2020-10-01,80.78,groceries
86 | 2020-11-01,71.81,groceries
87 | 2020-11-01,24.68,housing
88 | 2020-11-01,69.69,healthcare
89 | 2020-11-01,56.34,groceries
90 | 2020-12-01,66.58,entertainment
91 | 2020-12-01,76.24,utilities
92 | 2020-12-01,81.17,groceries
93 | 2020-12-01,47.01,car
94 | 2020-12-01,71.57,groceries
95 | 2020-12-01,73.81,utilities
96 | 2020-12-01,9.22,healthcare
97 | 2020-12-01,32.22,travel
98 | 2020-12-01,5.72,entertainment
99 | 2020-12-01,73.53,groceries
100 | 2021-01-01,70.84,entertainment
101 | 2021-01-01,99.42,entertainment
102 | 2021-01-01,37.04,healthcare
103 | 2021-01-01,90.98,healthcare
104 | 2021-01-01,58.67,healthcare
105 | 2021-01-01,92.68,groceries
106 | 2021-01-01,65.82,groceries
107 | 2021-01-01,99.58,groceries
108 | 2021-01-01,16.65,healthcare
109 | 2021-01-01,10.93,groceries
110 | 2021-02-01,33.45,groceries
111 | 2021-02-01,70.84,travel
112 | 2021-02-01,34.73,entertainment
113 | 2021-02-01,46.42,groceries
114 | 2021-02-01,91.4,healthcare
115 | 2021-02-01,71.29,travel
116 | 2021-02-01,21.49,entertainment
117 | 2021-02-01,18.48,groceries
118 | 2021-02-01,55.53,healthcare
119 | 2021-02-01,77.15,groceries
120 | 2021-03-01,43.56,groceries
121 | 2021-03-01,83.79,healthcare
122 | 2021-03-01,84.38,travel
123 | 2021-03-01,24.06,groceries
124 | 2021-03-01,56.91,groceries
125 | 2021-03-01,9.95,travel
126 | 2021-03-01,17.04,housing
127 | 2021-03-01,65.6,entertainment
128 | 2021-03-01,48.31,housing
129 | 2021-03-01,53.55,healthcare
130 | 2021-04-01,85.99,car
131 | 2021-04-01,59.48,travel
132 | 2021-04-01,1.1,travel
133 | 2021-04-01,54.48,groceries
134 | 2021-04-01,14.25,car
135 | 2021-04-01,24.01,groceries
136 | 2021-04-01,66.32,healthcare
137 | 2021-04-01,38.34,healthcare
138 | 2021-04-01,62.12,groceries
139 | 2021-04-01,14.14,housing
140 | 2021-04-01,64.07,groceries
141 | 2021-04-01,97.5,housing
142 | 2021-04-01,97.26,entertainment
143 | 2021-04-01,90.99,entertainment
144 | 2021-05-01,64.94,car
145 | 2021-05-01,36.64,groceries
146 | 2021-05-01,70.0,groceries
147 | 2021-05-01,90.62,groceries
148 | 2021-05-01,92.65,groceries
149 | 2021-05-01,77.18,groceries
150 | 2021-05-01,57.91,groceries
151 | 2021-05-01,43.15,housing
152 | 2021-06-01,83.8,groceries
153 | 2021-06-01,49.62,entertainment
154 | 2021-06-01,71.06,groceries
155 | 2021-06-01,33.25,groceries
156 | 2021-06-01,53.4,groceries
157 | 2021-06-01,4.61,entertainment
158 | 2021-06-01,2.51,utilities
159 | 2021-06-01,56.95,entertainment
160 | 2021-06-01,82.12,travel
161 | 2021-06-01,29.88,entertainment
162 | 2021-06-01,1.36,utilities
163 | 2021-07-01,40.01,housing
164 | 2021-07-01,47.98,groceries
165 | 2021-07-01,45.75,groceries
166 | 2021-07-01,95.4,groceries
167 | 2021-07-01,90.35,groceries
168 | 2021-07-01,82.36,groceries
169 | 2021-07-01,92.92,car
170 | 2021-07-01,16.85,entertainment
171 | 2021-07-01,82.77,groceries
172 | 2021-07-01,63.85,utilities
173 | 2021-07-01,10.82,entertainment
174 | 2021-08-01,42.11,groceries
175 | 2021-08-01,10.76,groceries
176 | 2021-08-01,49.48,entertainment
177 | 2021-08-01,53.11,groceries
178 | 2021-08-01,51.99,groceries
179 | 2021-08-01,75.16,utilities
180 | 2021-08-01,53.6,groceries
181 | 2021-08-01,37.27,groceries
182 | 2021-08-01,11.23,groceries
183 | 2021-09-01,45.43,groceries
184 | 2021-09-01,73.96,groceries
185 | 2021-09-01,45.1,groceries
186 | 2021-09-01,25.35,groceries
187 | 2021-09-01,68.04,groceries
188 | 2021-09-01,62.09,groceries
189 | 2021-09-01,66.98,utilities
190 | 2021-09-01,32.28,groceries
191 | 2021-09-01,19.8,entertainment
192 | 2021-09-01,69.23,groceries
193 | 2021-10-01,56.28,healthcare
194 | 2021-10-01,2.16,entertainment
195 | 2021-10-01,59.04,groceries
196 | 2021-10-01,89.95,groceries
197 | 2021-10-01,35.4,groceries
198 | 2021-10-01,75.07,entertainment
199 | 2021-10-01,34.26,groceries
200 | 2021-11-01,95.83,groceries
201 | 2021-11-01,9.02,utilities
202 | 2021-11-01,85.69,healthcare
203 | 2021-11-01,24.64,healthcare
204 | 2021-11-01,49.18,healthcare
205 | 2021-11-01,53.08,entertainment
206 | 2021-11-01,29.73,travel
207 | 2021-11-01,17.31,groceries
208 | 2021-11-01,76.38,groceries
209 | 2021-11-01,89.54,groceries
210 | 2021-12-01,53.16,groceries
211 | 2021-12-01,53.25,entertainment
212 | 2021-12-01,91.38,car
213 | 2021-12-01,77.33,entertainment
214 | 2021-12-01,85.64,groceries
215 | 2021-12-01,61.59,entertainment
216 | 2021-12-01,43.79,car
217 | 2021-12-01,44.01,travel
218 | 2021-12-01,13.41,healthcare
219 | 2022-01-01,96.68,housing
220 | 2022-01-01,18.45,groceries
221 | 2022-01-01,78.89,groceries
222 | 2022-01-01,6.87,healthcare
223 | 2022-01-01,56.18,entertainment
224 | 2022-01-01,63.06,groceries
225 | 2022-01-01,15.97,groceries
226 | 2022-01-01,50.25,groceries
227 | 2022-01-01,58.76,groceries
228 | 2022-01-01,24.09,groceries
229 | 2022-01-01,31.04,groceries
230 | 2022-01-01,70.04,groceries
231 | 2022-01-01,25.06,groceries
232 | 2022-01-01,61.61,travel
233 | 2022-01-01,39.21,groceries
234 | 2022-01-01,11.46,groceries
235 | 2022-02-01,14.8,housing
236 | 2022-02-01,81.78,healthcare
237 | 2022-02-01,12.49,entertainment
238 | 2022-02-01,68.73,utilities
239 | 2022-02-01,30.66,groceries
240 | 2022-02-01,56.6,groceries
241 | 2022-02-01,73.0,entertainment
242 | 2022-02-01,7.76,travel
243 | 2022-03-01,22.03,groceries
244 | 2022-03-01,74.89,groceries
245 | 2022-03-01,27.95,groceries
246 | 2022-03-01,97.88,travel
247 | 2022-03-01,29.95,entertainment
248 | 2022-03-01,33.46,entertainment
249 | 2022-03-01,11.64,entertainment
250 | 2022-03-01,28.7,groceries
251 | 2022-03-01,84.11,groceries
252 | 2022-03-01,65.94,healthcare
253 | 2022-03-01,68.89,groceries
254 | 2022-03-01,99.26,car
255 | 2022-03-01,19.47,car
256 | 2022-03-01,79.93,entertainment
257 | 2022-04-01,81.24,groceries
258 | 2022-04-01,32.05,utilities
259 | 2022-04-01,74.43,utilities
260 | 2022-04-01,92.54,entertainment
261 | 2022-04-01,67.87,entertainment
262 | 2022-04-01,49.54,groceries
263 | 2022-04-01,72.31,entertainment
264 | 2022-04-01,74.85,groceries
265 | 2022-04-01,56.05,groceries
266 | 2022-04-01,69.44,travel
267 | 2022-04-01,85.56,groceries
268 | 2022-05-01,14.99,healthcare
269 | 2022-05-01,29.46,groceries
270 | 2022-05-01,17.41,groceries
271 | 2022-05-01,83.34,groceries
272 | 2022-05-01,40.07,groceries
273 | 2022-05-01,78.19,entertainment
274 | 2022-05-01,32.96,housing
275 | 2022-05-01,89.92,entertainment
276 | 2022-06-01,86.82,groceries
277 | 2022-06-01,69.78,entertainment
278 | 2022-06-01,66.43,groceries
279 | 2022-06-01,58.21,groceries
280 | 2022-06-01,73.24,groceries
281 | 2022-06-01,19.85,groceries
282 | 2022-06-01,47.97,housing
283 |
--------------------------------------------------------------------------------
/part3/environment/README.md:
--------------------------------------------------------------------------------
1 | # Environment Setup
2 |
3 | ## Installation with Anaconda/Miniconda
4 |
5 | Run the two commands from the root directory.
6 |
7 | ```shell
8 | conda env create -f ./environment/conda.yaml
9 | conda activate dash-app-tutorial
10 | ```
11 |
12 | ## Installation with Pip
13 |
14 | **Note:** Python >= 3.9 is required to use the [modern style](https://peps.python.org/pep-0585/) of type annotations.
15 |
16 | Run the command from the root directory.
17 |
18 | ```shell
19 | python -m pip install -r ./environment/requirements.txt
20 | ```
21 |
--------------------------------------------------------------------------------
/part3/environment/conda.yaml:
--------------------------------------------------------------------------------
1 | name: dash-app-tutorial
2 | dependencies:
3 | - python>=3.9
4 | - pip
5 | - pip:
6 | - -r requirements.txt
7 |
--------------------------------------------------------------------------------
/part3/environment/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | black
2 | pylint
--------------------------------------------------------------------------------
/part3/environment/requirements.txt:
--------------------------------------------------------------------------------
1 | Babel
2 | dash
3 | dash-bootstrap-components
4 | pandas
5 | pandas-stubs
6 | plotly
7 | pyyaml
8 | python-i18n[YAML]
--------------------------------------------------------------------------------
/part3/locale/category.en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | car: Car
3 | entertainment: Entertainment
4 | groceries: Groceries
5 | healthcare: Medical/Healthcare
6 | housing: Mortgage/Rent
7 | travel: Vacation/Travel
8 | utilities: Utilities
9 |
--------------------------------------------------------------------------------
/part3/locale/category.nl.yml:
--------------------------------------------------------------------------------
1 | nl:
2 | car: Auto
3 | entertainment: Vermaak
4 | groceries: Boodschappen
5 | healthcare: Gezondheid/zorg
6 | housing: Hypotheek/huur
7 | travel: Vakantie/reizen
8 | utilities: Voorzieningen
9 |
--------------------------------------------------------------------------------
/part3/locale/general.en.yml:
--------------------------------------------------------------------------------
1 | ---
2 | en:
3 | amount: Amount
4 | app_title: Financial Dashboard
5 | category: Category
6 | month: Month
7 | no_data: No data selected
8 | select: Select...
9 | select_all: Select all
10 | year: Year
11 |
--------------------------------------------------------------------------------
/part3/locale/general.nl.yml:
--------------------------------------------------------------------------------
1 | ---
2 | nl:
3 | amount: Bedrag
4 | app_title: Financieel Dashboard
5 | category: Categorie
6 | month: Maand
7 | no_data: Geen gegevens geselecteerd
8 | select: Selecteer...
9 | select_all: Selecteer alles
10 | year: Jaar
11 |
--------------------------------------------------------------------------------
/part3/main.py:
--------------------------------------------------------------------------------
1 | import i18n
2 | from dash import Dash
3 | from dash_bootstrap_components.themes import BOOTSTRAP
4 |
5 | from src.components.layout import create_layout
6 | from src.data.loader import load_transaction_data
7 | from src.data.source import DataSource
8 |
9 | LOCALE = "nl"
10 | DATA_PATH = "./data/transactions.csv"
11 |
12 |
13 | def main() -> None:
14 |
15 | # set the locale and load the translations
16 | i18n.set("locale", LOCALE)
17 | i18n.load_path.append("locale")
18 |
19 | # load the data and create the data manager
20 | data = load_transaction_data(DATA_PATH, LOCALE)
21 | data = DataSource(data)
22 |
23 | app = Dash(external_stylesheets=[BOOTSTRAP])
24 | app.title = i18n.t("general.app_title")
25 | app.layout = create_layout(app, data)
26 | app.run()
27 |
28 |
29 | if __name__ == "__main__":
30 | main()
31 |
--------------------------------------------------------------------------------
/part3/src/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArjanCodes/2022-dash/a0e56e00c98900485b0c75e5d5ce99b36ac5a2d2/part3/src/__init__.py
--------------------------------------------------------------------------------
/part3/src/components/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArjanCodes/2022-dash/a0e56e00c98900485b0c75e5d5ce99b36ac5a2d2/part3/src/components/__init__.py
--------------------------------------------------------------------------------
/part3/src/components/bar_chart.py:
--------------------------------------------------------------------------------
1 | import i18n
2 | import plotly.express as px
3 | from dash import Dash, dcc, html
4 | from dash.dependencies import Input, Output
5 |
6 | from ..data.loader import DataSchema
7 | from ..data.source import DataSource
8 | from . import ids
9 |
10 |
11 | def render(app: Dash, source: DataSource) -> html.Div:
12 | @app.callback(
13 | Output(ids.BAR_CHART, "children"),
14 | [
15 | Input(ids.YEAR_DROPDOWN, "value"),
16 | Input(ids.MONTH_DROPDOWN, "value"),
17 | Input(ids.CATEGORY_DROPDOWN, "value"),
18 | ],
19 | )
20 | def update_bar_chart(
21 | years: list[str], months: list[str], categories: list[str]
22 | ) -> html.Div:
23 | filtered_source = source.filter(years, months, categories)
24 | if not filtered_source.row_count:
25 | return html.Div(i18n.t("general.no_data"), id=ids.BAR_CHART)
26 |
27 | fig = px.bar(
28 | filtered_source.create_pivot_table(),
29 | x=DataSchema.CATEGORY,
30 | y=DataSchema.AMOUNT,
31 | color="category",
32 | labels={
33 | "category": i18n.t("general.category"),
34 | "amount": i18n.t("general.amount"),
35 | },
36 | )
37 |
38 | return html.Div(dcc.Graph(figure=fig), id=ids.BAR_CHART)
39 |
40 | return html.Div(id=ids.BAR_CHART)
41 |
--------------------------------------------------------------------------------
/part3/src/components/category_dropdown.py:
--------------------------------------------------------------------------------
1 | import i18n
2 | from dash import Dash, dcc, html
3 | from dash.dependencies import Input, Output
4 |
5 | from ..data.source import DataSource
6 | from . import ids
7 | from .dropdown_helper import to_dropdown_options
8 |
9 |
10 | def render(app: Dash, source: DataSource) -> html.Div:
11 | @app.callback(
12 | Output(ids.CATEGORY_DROPDOWN, "value"),
13 | [
14 | Input(ids.YEAR_DROPDOWN, "value"),
15 | Input(ids.MONTH_DROPDOWN, "value"),
16 | Input(ids.SELECT_ALL_CATEGORIES_BUTTON, "n_clicks"),
17 | ],
18 | )
19 | def select_all_categories(years: list[str], months: list[str], _: int) -> list[str]:
20 | return source.filter(years=years, months=months).unique_categories
21 |
22 | return html.Div(
23 | children=[
24 | html.H6(i18n.t("general.category")),
25 | dcc.Dropdown(
26 | id=ids.CATEGORY_DROPDOWN,
27 | options=to_dropdown_options(source.unique_categories),
28 | value=source.unique_categories,
29 | multi=True,
30 | placeholder=i18n.t("general.select"),
31 | ),
32 | html.Button(
33 | className="dropdown-button",
34 | children=[i18n.t("general.select_all")],
35 | id=ids.SELECT_ALL_CATEGORIES_BUTTON,
36 | n_clicks=0,
37 | ),
38 | ],
39 | )
40 |
--------------------------------------------------------------------------------
/part3/src/components/dropdown_helper.py:
--------------------------------------------------------------------------------
1 | def to_dropdown_options(values: list[str]) -> list[dict[str, str]]:
2 | return [{"label": value, "value": value} for value in values]
3 |
--------------------------------------------------------------------------------
/part3/src/components/ids.py:
--------------------------------------------------------------------------------
1 | BAR_CHART = "bar-chart"
2 | PIE_CHART = "pie-chart"
3 |
4 | SELECT_ALL_CATEGORIES_BUTTON = "select-all-categories-button"
5 | CATEGORY_DROPDOWN = "category-dropdown"
6 |
7 | SELECT_ALL_MONTHS_BUTTON = "select-all-months-button"
8 | MONTH_DROPDOWN = "month-dropdown"
9 |
10 | YEAR_DROPDOWN = "year-dropdown"
11 | SELECT_ALL_YEARS_BUTTON = "select-all-years-button"
12 |
--------------------------------------------------------------------------------
/part3/src/components/layout.py:
--------------------------------------------------------------------------------
1 | from dash import Dash, html
2 | from src.components import (
3 | bar_chart,
4 | category_dropdown,
5 | month_dropdown,
6 | pie_chart,
7 | year_dropdown,
8 | )
9 |
10 | from ..data.source import DataSource
11 |
12 |
13 | def create_layout(app: Dash, source: DataSource) -> html.Div:
14 | return html.Div(
15 | className="app-div",
16 | children=[
17 | html.H1(app.title),
18 | html.Hr(),
19 | html.Div(
20 | className="dropdown-container",
21 | children=[
22 | year_dropdown.render(app, source),
23 | month_dropdown.render(app, source),
24 | category_dropdown.render(app, source),
25 | ],
26 | ),
27 | bar_chart.render(app, source),
28 | pie_chart.render(app, source),
29 | ],
30 | )
31 |
--------------------------------------------------------------------------------
/part3/src/components/month_dropdown.py:
--------------------------------------------------------------------------------
1 | import i18n
2 | from dash import Dash, dcc, html
3 | from dash.dependencies import Input, Output
4 |
5 | from ..data.source import DataSource
6 | from . import ids
7 | from .dropdown_helper import to_dropdown_options
8 |
9 |
10 | def render(app: Dash, source: DataSource) -> html.Div:
11 | @app.callback(
12 | Output(ids.MONTH_DROPDOWN, "value"),
13 | [
14 | Input(ids.YEAR_DROPDOWN, "value"),
15 | Input(ids.SELECT_ALL_MONTHS_BUTTON, "n_clicks"),
16 | ],
17 | )
18 | def select_all_months(years: list[str], _: int) -> list[str]:
19 | return source.filter(years=years).unique_months
20 |
21 | return html.Div(
22 | children=[
23 | html.H6(i18n.t("general.month")),
24 | dcc.Dropdown(
25 | id=ids.MONTH_DROPDOWN,
26 | options=to_dropdown_options(source.unique_months),
27 | value=source.unique_months,
28 | multi=True,
29 | ),
30 | html.Button(
31 | className="dropdown-button",
32 | children=[i18n.t("general.select_all")],
33 | id=ids.SELECT_ALL_MONTHS_BUTTON,
34 | n_clicks=0,
35 | ),
36 | ]
37 | )
38 |
--------------------------------------------------------------------------------
/part3/src/components/pie_chart.py:
--------------------------------------------------------------------------------
1 | import i18n
2 | import plotly.graph_objects as go
3 | from dash import Dash, dcc, html
4 | from dash.dependencies import Input, Output
5 |
6 | from ..data.source import DataSource
7 | from . import ids
8 |
9 |
10 | def render(app: Dash, source: DataSource) -> html.Div:
11 | @app.callback(
12 | Output(ids.PIE_CHART, "children"),
13 | [
14 | Input(ids.YEAR_DROPDOWN, "value"),
15 | Input(ids.MONTH_DROPDOWN, "value"),
16 | Input(ids.CATEGORY_DROPDOWN, "value"),
17 | ],
18 | )
19 | def update_pie_chart(
20 | years: list[str], months: list[str], categories: list[str]
21 | ) -> html.Div:
22 | filtered_source = source.filter(years, months, categories)
23 | if not filtered_source.row_count:
24 | return html.Div(i18n.t("general.no_data"), id=ids.PIE_CHART)
25 |
26 | pie = go.Pie(
27 | labels=filtered_source.all_categories,
28 | values=filtered_source.all_amounts,
29 | hole=0.5,
30 | )
31 |
32 | fig = go.Figure(data=[pie])
33 | fig.update_layout(margin={"t": 40, "b": 0, "l": 0, "r": 0})
34 | fig.update_traces(hovertemplate="%{label}
$%{value:.2f}")
35 |
36 | return html.Div(dcc.Graph(figure=fig), id=ids.PIE_CHART)
37 |
38 | return html.Div(id=ids.PIE_CHART)
39 |
--------------------------------------------------------------------------------
/part3/src/components/year_dropdown.py:
--------------------------------------------------------------------------------
1 | import i18n
2 | from dash import Dash, dcc, html
3 | from dash.dependencies import Input, Output
4 |
5 | from ..data.source import DataSource
6 | from . import ids
7 | from .dropdown_helper import to_dropdown_options
8 |
9 |
10 | def render(app: Dash, source: DataSource) -> html.Div:
11 | @app.callback(
12 | Output(ids.YEAR_DROPDOWN, "value"),
13 | Input(ids.SELECT_ALL_YEARS_BUTTON, "n_clicks"),
14 | )
15 | def select_all_years(_: int) -> list[str]:
16 | return source.unique_years
17 |
18 | return html.Div(
19 | children=[
20 | html.H6(i18n.t("general.year")),
21 | dcc.Dropdown(
22 | id=ids.YEAR_DROPDOWN,
23 | options=to_dropdown_options(source.unique_years),
24 | value=source.unique_years,
25 | multi=True,
26 | ),
27 | html.Button(
28 | className="dropdown-button",
29 | children=[i18n.t("general.select_all")],
30 | id=ids.SELECT_ALL_YEARS_BUTTON,
31 | n_clicks=0,
32 | ),
33 | ]
34 | )
35 |
--------------------------------------------------------------------------------
/part3/src/data/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArjanCodes/2022-dash/a0e56e00c98900485b0c75e5d5ce99b36ac5a2d2/part3/src/data/__init__.py
--------------------------------------------------------------------------------
/part3/src/data/loader.py:
--------------------------------------------------------------------------------
1 | import datetime as dt
2 | from functools import partial, reduce
3 | from typing import Callable
4 |
5 | import babel.dates
6 | import i18n
7 | import pandas as pd
8 |
9 | Preprocessor = Callable[[pd.DataFrame], pd.DataFrame]
10 |
11 |
12 | class DataSchema:
13 | AMOUNT = "amount"
14 | CATEGORY = "category"
15 | DATE = "date"
16 | MONTH = "month"
17 | YEAR = "year"
18 |
19 |
20 | def create_year_column(df: pd.DataFrame) -> pd.DataFrame:
21 | df[DataSchema.YEAR] = df[DataSchema.DATE].dt.year.astype(str)
22 | return df
23 |
24 |
25 | def create_month_column(df: pd.DataFrame) -> pd.DataFrame:
26 | df[DataSchema.MONTH] = df[DataSchema.DATE].dt.month.astype(str)
27 | return df
28 |
29 |
30 | def convert_date_locale(df: pd.DataFrame, locale: str) -> pd.DataFrame:
31 | def date_repr(date: dt.date) -> str:
32 | return babel.dates.format_date(date, format="MMMM", locale=locale)
33 |
34 | df[DataSchema.MONTH] = df[DataSchema.DATE].apply(date_repr)
35 | return df
36 |
37 |
38 | def translate_category_language(df: pd.DataFrame) -> pd.DataFrame:
39 | def translate(category: str) -> str:
40 | return i18n.t(f"category.{category}")
41 |
42 | df[DataSchema.CATEGORY] = df[DataSchema.CATEGORY].apply(translate)
43 | return df
44 |
45 |
46 | def compose(*functions: Preprocessor) -> Preprocessor:
47 | return reduce(lambda f, g: lambda x: g(f(x)), functions)
48 |
49 |
50 | def load_transaction_data(path: str, locale: str) -> pd.DataFrame:
51 | # load the data from the CSV file
52 | data = pd.read_csv(
53 | path,
54 | dtype={
55 | DataSchema.AMOUNT: float,
56 | DataSchema.CATEGORY: str,
57 | DataSchema.DATE: str,
58 | },
59 | parse_dates=[DataSchema.DATE],
60 | )
61 | preprocessor = compose(
62 | create_year_column,
63 | create_month_column,
64 | partial(convert_date_locale, locale=locale),
65 | translate_category_language,
66 | )
67 | return preprocessor(data)
68 |
--------------------------------------------------------------------------------
/part3/src/data/source.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from dataclasses import dataclass
4 | from typing import Optional
5 |
6 | import pandas as pd
7 |
8 | from ..data.loader import DataSchema
9 | from .loader import DataSchema
10 |
11 |
12 | @dataclass
13 | class DataSource:
14 | _data: pd.DataFrame
15 |
16 | def filter(
17 | self,
18 | years: Optional[list[str]] = None,
19 | months: Optional[list[str]] = None,
20 | categories: Optional[list[str]] = None,
21 | ) -> DataSource:
22 | if years is None:
23 | years = self.unique_years
24 | if months is None:
25 | months = self.unique_months
26 | if categories is None:
27 | categories = self.unique_categories
28 | filtered_data = self._data.query(
29 | "year in @years and month in @months and category in @categories"
30 | )
31 | return DataSource(filtered_data)
32 |
33 | def create_pivot_table(self) -> pd.DataFrame:
34 | pt = self._data.pivot_table(
35 | values=DataSchema.AMOUNT,
36 | index=[DataSchema.CATEGORY],
37 | aggfunc="sum",
38 | fill_value=0,
39 | dropna=False,
40 | )
41 | return pt.reset_index().sort_values(DataSchema.AMOUNT, ascending=False)
42 |
43 | @property
44 | def row_count(self) -> int:
45 | return self._data.shape[0]
46 |
47 | @property
48 | def all_years(self) -> list[str]:
49 | return self._data[DataSchema.YEAR].tolist()
50 |
51 | @property
52 | def all_months(self) -> list[str]:
53 | return self._data[DataSchema.MONTH].tolist()
54 |
55 | @property
56 | def all_categories(self) -> list[str]:
57 | return self._data[DataSchema.CATEGORY].tolist()
58 |
59 | @property
60 | def all_amounts(self) -> list[str]:
61 | return self._data[DataSchema.AMOUNT].tolist()
62 |
63 | @property
64 | def unique_years(self) -> list[str]:
65 | return sorted(set(self.all_years), key=int)
66 |
67 | @property
68 | def unique_months(self) -> list[str]:
69 | return sorted(set(self.all_months))
70 |
71 | @property
72 | def unique_categories(self) -> list[str]:
73 | return sorted(set(self.all_categories))
74 |
--------------------------------------------------------------------------------