-
Notifications
You must be signed in to change notification settings - Fork 1
/
06-appdev-code.Rmd
1039 lines (706 loc) · 46.2 KB
/
06-appdev-code.Rmd
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
output:
html_document:
code_folding: hide
---
# Coding for app development
## Getting Started with Github and Github Pages
In these exercises we'll begin working with a code sharing platform called [Github](https://github.com) that is used by software developers, researchers, and hobbyists all around the world. With a free account on Github, you are able to upload code bases and datasets into public repositories, allowing other people to see your work, contribute to it, or build from it.
One especially useful tool that the platform provides is called Github Pages, a mechanism that allows you to turn any single repository into a publicly accessible website for free. Github Pages is a perfect way to create a personal website, blog, or portfolio site, but we can also use it to host and serve simple web maps and visualizations. The following tips will help you get started with Github.
### What is a Repository?
A repository is any collection of code or data that is stored together as a unit. For example, the entire code base for [Moodle](https://github.com/moodle/moodle), or [covid-19-data] downloads from the NY Times.
### Understanding Github's URL Structure - Naming is important!
Github is organized with a simple hierarchy: Account > Repository Name, where accounts can be either individual users, or organizational accounts. For example, our HeRoP lab organization is [healthyregions](https://github.com/healthyregions) which means that our repository "sdohplace-toolkit" (this toolkit!) is located at [https://github.com/healthyregions/sdohplace-toolkit](https://github.com/healthyregions/sdohplace-toolkit).
### Creating an Account
When you choose a username, pick something simple that can be shared easily, because, as described above, your username will be in public urls all the time. Additionally, when you publish a repository with Github Pages, that user name will become part of the URL for your page.
## Leaflet Map with CSV and GeoJSON data
Leaflet is a very popular open source mapping library used to create interactive web maps. Leaflet is written in JavaScript, a programming language that runs in all web browsers, so creating a map with Leaflet will involve at least a bit of looking at and modifying code--HMTL, CSS, and JavaScript which are the foundational components of any webpage.
:::tip
**Tip**
Our example builds from [Leaflet Maps with CSV Data](https://handsondataviz.org/leaflet-maps-with-csv.html) from [HandsOnDataViz](https://handsondataviz.org/), a fantastic collection of guides and recipes for data visualization by Jack Dougherty and Ilya Ilyankou that focus on using open source and accessible technologies. We strongly encourage you to explore all of their other content as well!
:::
Generally speaking, libraries like Leaflet create web maps by defining an area of a web page, like a canvas, and then loading various geospatial data into that area, allowing users to pan, zoom, inspect, and interact with the content. In this example, we will create and publish a very simple web map using prepared CSV and GeoJSON datasets. At the end of the exercise, you should be able to swap these datasets out with your own, and have a basic understanding of how to modify Leaflet code.
We will do all of our file storage and editing directly in [Github](https://github.com), which will also allow us to immediately make our map publicly visible.
To get started, head to our template repository: [https://github.com/healthyregions/leaflet-asset-map](https://github.com/healthyregions/leaflet-asset-map).
1. Click the **Use this template** button to create a copy of this repository in your own GitHub account.
2. Configure Github Pages using the Static HTML workflow
- In **Settings** > **Pages** set the **Source** dropdown to **Github Actions**
- Choose the **Static HTML** option and click **Configure**
- Don't make any changes to the code you see yet, just click **Commit changes..**
- Wait a few seconds and visit https://<username>.github.io/leaflet-asset-map. You should see a zoomable map with markers on it.
3. Upload your own CSV or GeoJSON file to the data directory.
- Make sure your CSV has columns that hold Latitude and Longitude values for each feature.
4. Follow the examples in example.html to add your own layers to the main map in index.html.
For more information on customization, see [Leaflet documentation](https://leafletjs.org).
For valid icon names, see [ionic.io/ionicons/v2](https://ionic.io/ionicons/v2). Click an icon, and use the name you see without including the ion- prefix.
## Thematic Map with HTML & CSS
### Getting Started {-}
You have some experience working with Github *within* Github, the website. Now let's bring Github to your own computer!
- Set up a "Code" folder on your computer somwhere that is easy to navigate. This will store your Github coding projects.
- Download [Github Desktop](https://desktop.github.com/). Direct it to upload new repositories to this folder.
- Download a coding editor software for your computer. Popular ones are [Visual Studio](https://code.visualstudio.com/) or [Sublime Text](https://www.sublimetext.com/). When it doubt, google and research on your own!
You could also use RStudio/Posit cloud, but it may not be optimized for all coding languages.
#### Start a New Repository {-}
In Github (the website), create a *New Repository*. Give it a name, a description, and make it public. In this case, we're calling our project "NYC-Map". Add a "README" file to leave more details and descriptions for yourself later. Click on "Create Repository" at the bottom of the page.
![](images/06-github-start.png)
Next, click on the big green button, "Code", and select "Open in Github Desktop.
![](images/06-github-clone.png){width=95%}
You are *cloning the repository* you just made, and adding a copy to your own computer. Select the right path to access your coding project.
![](images/06-clone.png){width=35%}
### Start Coding
- Open up your coding project folder. You'll see the README file that was initialized in the Repo.
- In your code editing software, create a new file, and name it "index.html."
![](images/06-index-start.png)
### Basics of an HTML page
In our very simple application, we will use the basics of an html page: the head and body.
- In the `head`, we'll add some metadata like the title of our map. Additionally, crucial libraries for styling and functionalities will be loaded in as CSS and Javascript (JS) links.
- In the `body`, we'll prepare to bring in two divs, or "divisions." One will be a map div, that calls the mapbox basemap we made in an earlier module. The other will overlay a transparent panel that will serve as our legend. We add a heading 1 level title, "NYC Map" to start.
Learn more about the basics of HTML using free online educational tools like [W3 Schools](https://www.w3schools.com/html/html_basic.asp). For now, it's okay to just copy and paste the information below.
<details markdown='1'><summary>Show HTML App Code</summary>
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>NYC Map </title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=yes" />
<!-- CSS only -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<link href="https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.css" rel="stylesheet">
<link href="main-style.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:opsz,wght@6..12,200;6..12,300;6..12,400;6..12,700&family=Roboto+Slab:wght@400;500&display=swap" rel="stylesheet">
<!-- JS only -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
<script src="https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; };
</style>
</head>
<body>
<div class="panel">
<h1 > NYC Map </h1>
</div>
<div id='map'> </div>
</body>
```
</br></br></details>
To render the html page, simple drag and drop the .html file from your folder on your computer into a web browser. Right now, there should be nothing except a title, "NYC Map" -- we've just loaded in some libraries, and that's it!
#### Add your Mapbox Map {-}
Using the code snippet provided from Mapbox, we can add our basemap. (If you're not sure what we're referring to here, check out the Mapbox section in the main Module 6 chapter.) Add this script after the map div, and within the body section.
Use your own Mapbox token -- copy and paste to replace the text, "YOUR MAPBOX API TOKEN."
<details markdown='1'><summary>Show Mapbox Script </summary>
```html
<script>
<!-- Temp -- Need to push to Github Environment Secret -->
mapboxgl.accessToken = 'YOUR MAPBOX API TOKEN';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/cdiscenza/clvmny7ym06yv01nug1kbefwd',
zoom: 10,
minZoom: 5.3,
center: [-74.03638858449402 , 40.68048994718785]
});
// Add zoom and rotation controls to the map.
var nav = new mapboxgl.NavigationControl();
map.addControl(nav, 'top-right');
</script>
```
</br></br></details>
Refresh your app in your web browser! It should look like this:
![](images/06-mapbox-map.png){width=95%}
:::pitfall
**Pitfall**
In this example, we are exposing an **API token** as a learning activity. You would be running this on your own private computer for testing. However, you won't want to expose your token outside of this environment.
As you get to the end of this exercise, once your map is working and served, check out this detailed, [step-by-step tutorial](https://github.com/healthyregions/leaflet-asset-map?tab=readme-ov-file#adding-api-keys) about how to hide your API token using Github Secrets when serving your app through Github Pages!
::::
### Add a Panel Legend
We're going to hack out a legend. Experienced coders, avert your eyes! The official way to do this can be found via a [mapbox tutorial](https://docs.mapbox.com/help/tutorials/choropleth-studio-gl-pt-2/). Here, let's generate a temporary option with less fuss. We'll "hard code" the data classification intervals into our legend. Using our panel and HTML, we'll add a description of the data we have in our map.
#### Add custom CSS {-}
Generate a new file in your coding project called `main-style.css`. Copy and past the following code, and save.
<details markdown='1'><summary>Show Custom CSS Code </summary>
```css
.panel {
position: absolute;
top: 50px;
left: 40px;
width: 380px;
max-height: 660px;
opacity: .9;
background: #fff ;
color: #545454;
padding: 20px 24px 12px 24px;
height: 85%;
overflow-x: hidden;
overflow-y: auto;
outline: none;
z-index: 9092;
border-radius: 0px 0px 10px 0px;
}
h1 {
font-family: 'Nunito Sans', serif;
font-weight: 900;
}
p {
font-family: 'Nunito Sans', sans-serif;
font-weight: 400;
}
p.temp {
font-family: 'Nunito Sans', serif;
font-weight: 300;
font-size: 12px;
line-height: 1.2;
}
a {
font-family: 'Nunito Sans', sans-serif;
color: #2f5aa8;
}
```
</br></br></details>
:::tip
**Tip**
Note the font family selected here, 'Nunito Sans.' A quick way to make your app stand out is playing with font families! Check out free [Google Fonts](https://fonts.google.com/) to bring more fonts in your system. Some tips on how we're using them here:
- Brought in the font family as a stylesheet in our main `index.html` file, in the header:
`<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:opsz,wght@6..12,200;6..12,300;6..12,400;6..12,700&family=Roboto+Slab:wght@400;500&display=swap" rel="stylesheet">`
- Specify font families in a customized CSS file, `main-style.css`.
::::
#### "Hack" a Legend {-}
Go back to your Mapbox account, and record the intervals of each bin of your choropleth map. Take a screenshot of each corresponding color swatch; then, rename those swatches something easy to input, like `1.png`. Bring all of these image swatches to a new folder called "images" in your coding project folder.
![](images/06-images.png){width=40%}
This is a prototype -- not a final project! The goal is to get something working quickly, and it may not be pretty when you open the hood. But, it's possible to get a working app with some HTML/CSS, sweat, and grease! Have fun with the process.
#### Add to your Panel {-}
In your main index file, you can now add more content to your panel. Give the map panel some additional helper text using the "text-muted" class, and add horizontal lines using the `<hr>` tag to keep it classy. Use the legend swatches, resized, and update the corresponding interval.
<details markdown='1'><summary>Show index.html Panel Code </summary>
```html
<div class="panel">
<h1 > NYC Neighborhood & Health Map </h1>
<p class="text-muted"> Health Equity across City Neighborhoods. See XXX for more details. </a> </p>
<hr>
<h5> Proportion of Neighborhood Residents Self-Identified as Black or African American </h5>
<p><img src="images/6.png" height="15"><b> 0.0% </b></p>
<p><img src="images/5.png" height="15"><b> 0.01 - 22.77% </b></p>
<p><img src="images/4.png" height="15"><b> 22.78 - 45.54% </b></p>
<p><img src="images/3.png" height="15"><b> 45.55 - 68.2% </b></p>
<p><img src="images/2.png" height="15"><b> 68.3 - 91.0% </b></p>
<p><img src="images/1.png" height="15"><b> 91.1% </b></p>
<hr>
<p class="temp"> <b>Data Sources:</b> NYC Data, 2019. </p>
</div>
```
</br></br></details>
### Finalize and Push
Run the app in your browser as you go to ensure you can troubleshoot any bugs that come up.
![](images/06-final-mapbox.png)
<details markdown='1'><summary>Final index.html Code </summary>
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=yes" />
<!-- CSS only -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<link href="https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.css" rel="stylesheet">
<link href="main-style.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:opsz,wght@6..12,200;6..12,300;6..12,400;6..12,700&family=Roboto+Slab:wght@400;500&display=swap" rel="stylesheet">
<!-- JS only -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
<script src="https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; };
</style>
</head>
<body>
<div class="panel">
<h1 > NYC Neighborhood & Health Map </h1>
<p class="text-muted"> Health Equity across City Neighborhoods. See XXX for more details. </a> </p>
<hr>
<h5> Proportion of Neighborhood Residents Self-Identified as Black or African American </h5>
<p><img src="images/6.png" height="15"><b> 0.0% </p>
<p><img src="images/5.png" height="15"><b> 0.01 - 22.77% </p>
<p><img src="images/4.png" height="15"><b> 22.78 - 45.54% </p>
<p><img src="images/3.png" height="15"><b> 45.55 - 68.2% </p>
<p><img src="images/2.png" height="15"><b> 68.3 - 91.0% </p>
<p><img src="images/1.png" height="15"><b> 91.1% </p>
<hr>
<p class="temp"> <b>Data Sources:</b> NYC Data, 2019. </p>
</div>
<div id='map'> </div>
<script>
<!-- Temp -- Need to push to Github Environmental Secret -->
mapboxgl.accessToken = 'YOUR MAPBOX API TOKEN';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/cdiscenza/clvmny7ym06yv01nug1kbefwd',
zoom: 10,
minZoom: 5.3,
center: [-74.03638858449402 , 40.68048994718785]
});
// Add zoom and rotation controls to the map.
var nav = new mapboxgl.NavigationControl();
map.addControl(nav, 'top-right');
</script>
</body>
</html>
```
</br></details></br></details>
<details markdown='1'><summary> Push local code up back to Github's main servers. </summary>
- Go back to Github Desktop. Add a short comment (ex. "push initial map"), Commit to Main, and then Push to Origin.
![](images/06-github-push.png){width=95%}
</br></details></br></details>
<details markdown='1'><summary> Confirm things worked, and serve your map </summary>
Return to Github's website repository. You'll see your updates live on the Github site. Finally, use what you learned previously to serve your map using Github Pages.
</br></details></br></details>
When you're ready, work through additional [Github Tutorials](https://docs.github.com/en/get-started/start-your-journey/git-and-github-learning-resources) to get more familiar with the "push and pull" process of working with code this way. This is just the beginning!
### Resources {-}
- Check out (mostly) free resources like [W3 Schools](https://www.w3schools.com/) to learn more basics on HTML, CSS, Javascript, and more. Expect to make lots of mistakes, and learn froom them at each step!
- Mapbox also has many free step-by-step [tutorials](https://docs.mapbox.com/help/tutorials/) that link back to an application you're developing.
- For a comprehensive appraoch, check out the [Web Mapping: A Workbook for Interactive Cartography and Visualization on the Open Web](https://github.com/uwcartlab/webmapping) by Robert Roth and colleagues at UWisconsin, Madison. This will take you through workspace set up, scripting debugging tips, Leaflet and D3 (Plotly) exercises, and more.
## Dashboards with R Shiny
### Getting Started {-}
Once you have a solid sense of R (see previous modules and recommended tutorials), you may be ready to make your first app! To develop an application quickly, we use the `shiny` package. Shiny is a web application framework for R that makes it easy to build interactive web apps straight from R. This particular application allows users to explore various demographic metrics through interactive maps and charts.
```{r eval=FALSE}
install.packages("shiny")
```
We recommend going through the [beginner lessons on Shiny applications](https://shiny.posit.co/r/getstarted/shiny-basics/lesson1/index.html) at Posit before diving into your app development directly. Get familiar with the basics, practice, and explore different example apps for ideas.
![](images/6-shinyintro.png){width=95%}
As you go through these, resist the urge to try to incorporate everything into your own app. Follow the design-thinking process, user input, and diagrams you built in previous modules!
A Shiny app can be contained in a single script, `app.R`, which will have the following three components:
- a user interface object
- a server function
- a call to the `shinyApp` function
### User Interface
You can define the layout of your application using the user interface, defining what goes where, how it looks, and what events are triggered. Upon loading the site, a default plot may be triggered to be output. A user may also select specific items within the user interface, like a variable from a drop-down panel, or sliding a slider.
Using the [example](https://shiny.posit.co/r/getstarted/shiny-basics/) from Shiny's official tutorial, copy and paste the following into a new file you'll save as `app.R` in a folder on your computer.
To run within RStudio, click the "Knit" icon at the top of your code.
```{r class.source = 'fold-hide', eval=FALSE}
library(shiny)
library(bslib)
# Define UI ----
ui <- page_sidebar(
)
# Define server logic ----
server <- function(input, output) {
}
# Run the app ----
shinyApp(ui = ui, server = server)
```
Following the same example, add a title to your application, a sidebar, and a main section. In this example, we'll make a an on SDOH indicators in NYC.
```{r class.source = 'fold-hide', eval=FALSE}
library(shiny)
library(bslib)
# Define UI ----
ui <-
page_sidebar(
title = "NYC SDOH App",
sidebar = sidebar("sidebar"),
"main contents"
)
# Define server logic ----
server <- function(input, output) {
}
# Run the app ----
shinyApp(ui = ui, server = server)
```
Next, let's add a drop down variable selection for a variable of interest we'd like to explore in the app. For example, we may want to examine data by self-identified race and ethnicity, as reported by neighborhood via the Census. We'll add a drop-down widget, and "helper text" to explain what the user should do.
Try to do this on your own first. Then, check the code below!
<details markdown='1'><summary>Show Code</summary>
```{r class.source = 'fold-hide', eval=FALSE}
library(shiny)
library(bslib)
# Define UI ----
ui <-
page_sidebar(
title = "NYC SDOH App",
sidebar = sidebar(
helpText("Select different variables from the dropdown menus to explore the data."),
selectInput("color", "Self-Identified Race & Ethnicity:",
choices = c("Percent Black" = "pctblack",
"Percent Hispanic" = "pcthisp",
"Percent White" = "pctwhite"),
selected = "pctblack"),
),
)
# Define server logic ----
server <- function(input, output) {
}
# Run the app ----
shinyApp(ui = ui, server = server)
```
</br></br></details>
Ensure you're running each time that you add, edit, or change anything. This helps with the troubleshooting process! By now, your application will be looking like this:
![](images/06-shiny1.png){width=95%}
Continue to explore the different layouts, widgets, themes, and options available to you in the Shiny documentation.
### Server
When we're running the application, we're actually using our computer as the server. Let's connect our dropdown to data we'll load in -- NYC data -- and use the user selection to generate a map.
We recommend getting the script to work in an R script on its own before plugging in, to confirm that it will work the way you need it to. De-bugging can be tricky in more complex applications, so anything you can do to support your process will be beneficial.
We'll jump a few steps ahead, to show what the full set up can look like for adding a map that is linked to user input:
<details markdown='1'><summary>Show Full App Code</summary>
```{r eval=FALSE}
library(shiny)
library(leaflet)
library(sf)
library(plotly)
library(dplyr)
library(stringr)
# Load data ----
nyc_data <- st_read("NYC_nbrhd_data.geojson", quiet = TRUE)
nyc_data <- st_make_valid(nyc_data)
nyc_data_df <- st_drop_geometry(nyc_data)
map_data <- st_transform(nyc_data, crs = 4326)
# Define UI ----
ui <-
page_sidebar(
title = "NYC SDOH App",
sidebar = sidebar(
helpText("Select different variables from the dropdown menus to explore the data."),
selectInput("color", "Self-Identified Race & Ethnicity:",
choices = c("Percent Black" = "pctblack",
"Percent Hispanic" = "pcthisp",
"Percent White" = "pctwhite"),
selected = "pctblack")),
mainPanel(
## Add a map
(leafletOutput("map", width = "100%")
)))
# Define server logic ----
server <- function(input, output, session) {
# Map output for Racial Demographics
output$map <- renderLeaflet({
valid_data <- map_data[!is.na(map_data[[input$color]]), ]
pal <- colorQuantile("PuBuGn", valid_data[[input$color]], n = 5)
leaflet(valid_data) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addPolygons(
fillColor = ~pal(valid_data[[input$color]]),
fillOpacity = 0.7, weight = 1, color = "white",
popup = ~paste(NTAName, "<br>",
paste(input$color, ":", round(valid_data[[input$color]], 2), "%"))
) %>%
setView(lng = -73.935242, lat = 40.730610, zoom = 10)
})
}
# Run the app ----
shinyApp(ui = ui, server = server)
```
</br></br></details>
Which renders the following:
![](images/5-ShinyAppV1.png){width=95%}
Inspect how the map output call was added to the UI. In the server function, the map data is slightly cleaned (from debugging in a script on its own), and a color palette is indicated using a ColorBrewer selection. Then, we use leaflet to visualize the variable selected by the user. We have found that leaflet is more responsive than tmap when using Shiny apps, though be sure to explore new options on your own!
### Expand Your Prototype
It's easier to get a basic prototype up and running. Now the fun and frustrating part begins! Start editing, updating, refining, and getting closer to your final goal. Multiple updates are included in these refinements of our expanded prototype across UI and Server settings, including:
- Addition of new tabs with additional variables, visualizations, and content about the project.
- Integration of a new Shiny library, 'shinythemes'. After exploring how to add and update a new theme, the library was installed, and a call made in the final part of the application. (Hint: look for the 'yeti' theme in the final code.) Get more ideas at [Shiny Themes](https://rstudio.github.io/shinythemes/).
- Improvements on performance via lots of 'tinkering' and testing. A slow scatter plot was fixed by removing the spatial components of the dataset, using the `st_drop_geometry()` call in the `sf` framework. A list of neighborhood names was made alphabetical. And so much more...
Look for some of the "easter eggs" in code snippets below, and see how you can improve and refine further! The app could get even more fine-tuned, "reactive" (and less repetitive in coding output), and styled further.
#### Updated User Interface {-}
In our extension, we want the final UI organize into three main tabs, allowing users to interactively explore different facets of the NYC neighborhood data. For this example, we'll try using tabs instead of a sidebar. Try blending both styles, and keeping the bootstrap library used in our initial example as a challenge.
##### Tab 1: Map & Racial Demographics {-}
In the first tab, we'll features a map and a demographic chart, with controls for selecting demographic variables and neighborhoods. Here are the explanation for some functions that we used below:
- `selectInput` for variable of interest: Allows users to select which `variable` statistic to visualize on the map.
- `selectInput` for neighborhood: Enables users to pick a specific neighborhood for detailed demographic breakdown in the chart.
- `leafletOutput` and `plotlyOutput`: Reserved spaces in the UI for displaying the map and the chart respectively.
<details markdown='1'><summary>Show Tab 1 UI Code </summary>
```{r eval=FALSE}
tab1_ui <- tabPanel("Self-Identified Race & Ethnicty",
sidebarLayout(
sidebarPanel(
p("Select different variables from the dropdown menus to explore the data."),
selectInput("race", "Self-Identified Race & Ethnicity:",
choices = c("Percent Asian & Pacific Islander" = "pctapi",
"Percent Black" = "pctblack",
"Percent Hispanic" = "pcthisp",
"Percent White" = "pctwhite",
"Percent Other Identified Race" = "pctother"),
selected = "pctblack"),
selectInput("neighborhood", "NYC Neighborhood:",
choices = str_sort(nyc_data$NTAName),
selected = "Pelham Bay-Country Club-City Island"),
helpText("Data source: NYC Neighborhood Data"),
br(),
h3("Racial & Ethnic Disparities"),
p("Extensive research has shown that racial and ethnic disparities in
quality of care and use of services exist and persist in the United States.
Disparities may emerge from unequal access to health care, critical resources
such as health foods, housing, and transportation."),
br(),
p("Explore racial and ethnic population distributions by NYC neighborhood in this tab,
and then explore socioeconomic and health trends acrosos the rest of the applications.
Identify locations for further analysis."),
helpText("Read More: The Commonwealth Fund 2024 State Health Disparities Report")
),
mainPanel(
fluidRow(leafletOutput("map"),
br(),
fluidRow(plotlyOutput("racialDemoChart"))
))
)
)
```
</br></br></details>
##### Tab 2: Socioeconomic Demographics {-}
Similar in structure to Tab 1, now let's create the Tab 2, which focuses on socioeconomic indicators such as poverty levels and rent burden.
<details markdown='1'><summary>Show Tab 2 UI Code </summary>
```{r eval=FALSE}
tab2_ui <- tabPanel("Socioeconomic Demographics",
sidebarLayout(
sidebarPanel(
selectInput("color_socio", "Demographic variable:",
choices = c("Percent in Poverty" = "pctpov",
"Rent < 30% of Income" = "rent.30",
"Rent < 50% of Income" = "rent.50"),
selected = "pctpov"),
selectInput("neighborhood_socio", "Select Neighborhood:",
choices = str_sort(nyc_data$NTAName),
selected = "Pelham Bay-Country Club-City Island"),
helpText("Data source: NYC Neighborhood Data"),
br(),
h3("Socioeconomic Disparities"),
p("Nulla suscipit, purus ac varius sagittis, velit lorem condimentum ipsum, sit amet auctor sem tellus a leo. Aenean faucibus hendrerit diam non rutrum. Proin nec nisi dolor. Nam egestas dolor sapien, eget pellentesque neque tincidunt nec. Phasellus mattis pulvinar tincidunt. Phasellus eget condimentum nisl. Praesent dapibus dui elit, id fringilla quam interdum vel. Praesent vestibulum nulla et rutrum ornare. Donec cursus felis dui, et auctor nisi pulvinar ac. Suspendisse placerat ex sed arcu semper volutpat. Donec commodo consequat ornare. Aenean est lectus, semper at luctus sit amet, bibendum vitae augue. Donec risus felis, commodo eget tristique vitae, imperdiet in risus."),
helpText("Read More: Include text here ")
),
mainPanel(
fluidRow(leafletOutput("map_socio"),
br(),
fluidRow(plotlyOutput("socioDemoChart"))
)
)
)
)
```
</br></br></details>
##### Tab 3: Severe Maternal Morbidity & Preterm Birth Rates {-}
In Tab 3, we want to introduces more health-related variables, displaying a map and a scatter plot. The scatter plot takes time to load, so further de-bugging may be necessary. Try alternate libraries, styles, and new approaches to refine further.
<details markdown='1'><summary>Show Tab 3 UI Code </summary>
```{r eval=FALSE}
tab3_ui <- tabPanel("Severe Maternal Morbidity & Preterm Birth Rates",
sidebarLayout(
sidebarPanel(
selectInput("color_health", "Health variable:",
choices = c("Severe Maternal Morbidity Rate" = "smmrate",
"Preterm Birth Rate" = "ptbrate"),
selected = "smmrate"),
helpText("Data source: NYC Neighborhood Data"),
br(),
h3("Maternal Health Outcomes"),
p("Pellentesque nisl ipsum, bibendum non porttitor eget, lobortis sit amet arcu. Aliquam et erat nec nisi fermentum aliquet non a massa. Mauris vel sapien justo. Sed fermentum sed purus ut fringilla. Aliquam pulvinar, ligula ac ornare rutrum, est ipsum tristique metus, non imperdiet nibh ligula id elit. Proin ac dui in ligula finibus facilisis. Quisque at vulputate nulla, sit amet varius nunc. In eu cursus quam. In diam est, tristique sit amet nunc nec, vehicula hendrerit odio. Phasellus est turpis, vulputate eu suscipit sit amet, semper at enim. Vivamus sit amet risus leo. Vestibulum porttitor feugiat ipsum, ut volutpat erat pharetra quis. Suspendisse interdum ultrices nisi vel finibus. Aliquam lobortis sed arcu eget ornare."),
helpText("Read More: Include text here ")
),
mainPanel(
fluidRow(leafletOutput("map_health"),
br(),
fluidRow(plotlyOutput("healthScatterChart"))
)
)
)
)
```
</br></br></details>
##### Tab 4: About {-}
Finally, we also want to include a Tab 4 that provides contextual information about the application, explaining its purpose and the data source.
We are making a prototype, so use some ["Lorem Ipsum"](https://loremipsum.io/) placeholder language that we can update in the future.
![](images/06-shiny-about.png)
<details markdown='1'><summary>Show Tab 4 About Code </summary>
```{r eval=FALSE}
tab4_ui <- tabPanel("About",
sidebarLayout(
sidebarPanel(
h3("Data"),
p("Mauris vel sapien justo. Sed fermentum sed purus ut fringilla. Aliquam pulvinar,
ligula ac ornare rutrum, est ipsum tristique metus, non imperdiet nibh ligula id
elit."),
br(),
h3("Methodology"),
p("Proin ac dui in ligula finibus facilisis. Quisque at vulputate nulla, sit
amet varius nunc. In eu cursus quam. In diam est, tristique sit amet nunc nec,
vehicula hendrerit odio. "),
helpText("Read More: Include text here ")
),
mainPanel(
fluidRow(
h2("Motivations & Background"),
p("Phasellus est turpis, vulputate eu suscipit sit amet,
semper at enim. Vivamus sit amet risus leo. Vestibulum porttitor feugiat ipsum,
ut volutpat erat pharetra quis. Suspendisse interdum ultrices nisi vel finibus.
Aliquam lobortis sed arcu eget ornare."),
br(),
h2("Study Findings"),
p("Phasellus est turpis, vulputate eu suscipit sit amet,
semper at enim. Vivamus sit amet risus leo. Vestibulum porttitor feugiat ipsum,
ut volutpat erat pharetra quis. Suspendisse interdum ultrices nisi vel finibus.
Aliquam lobortis sed arcu eget ornare."),
br(),
h2("Team"),
p("Phasellus est turpis, vulputate eu suscipit sit amet,
semper at enim. Vivamus sit amet risus leo. Vestibulum porttitor feugiat ipsum,
ut volutpat erat pharetra quis. Suspendisse interdum ultrices nisi vel finibus.
Aliquam lobortis sed arcu eget ornare."),
br(),
h2("Questions? Contact Us."),
p("Email person@person.com for more information."),
))))
```
</br></br></details>
#### Define Server Logic
After define the user interface, we will move on to define the server logic. It processes user inputs from the UI and updates the outputs (maps and charts). It dynamically reacts to user interactions such as selecting a neighborhood or a demographic variable.
##### Server Logic for Tabs {-}
Now, we will create server code to handle the dynamic visualization of racial demographics within New York City neighborhoods in Tab 1. It renders an interactive map and a bar chart based on user inputs, showing the distribution of different racial groups. The map highlights neighborhoods with varying demographic densities, while the bar chart provides detailed statistics for a selected neighborhood.
The server functions for other tabs follow a similar structure but focus on different data attributes. So, let's define the server logic for other three tabs.
<details markdown='1'><summary>Show Tab 1 Server Code </summary>
```{r eval=FALSE}
tab1_server <- function(input, output, session) {
# Map output for Racial Demographics
output$map <- renderLeaflet({
valid_data <- nyc_data[!is.na(nyc_data[[input$race]]), ]
pal <- colorQuantile("viridis", valid_data[[input$race]], n = 5)
leaflet(valid_data) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addPolygons(
fillColor = ~pal(valid_data[[input$race]]),
fillOpacity = 0.7, weight = 1, color = "white",
popup = ~paste(NTAName, "<br>",
paste(input$race, ":", round(valid_data[[input$race]], 2), "%"))
) %>%
addLegend(
position = "bottomright",
pal = pal,
values = valid_data[[input$race]],
title = "% of population"
) %>%
setView(lng = -73.935242, lat = 40.730610, zoom = 10)
})
# Racial Demographics chart
output$racialDemoChart <- renderPlotly({
chart_data <- nyc_data[nyc_data$NTAName == input$neighborhood, ]
# Extract data and remove "geometry" column
racial_data <- st_drop_geometry(chart_data)
racial_data <- racial_data[, c("pctblack", "pcthisp", "pctwhite", "pctapi", "pctother")]
racial_data <- t(racial_data)
racial_data <- as.data.frame(racial_data)
racial_data <- cbind(Race = rownames(racial_data), Percentage = racial_data[, 1])
rownames(racial_data) <- NULL
plot_ly(data = as.data.frame(racial_data), x = ~Race, y = ~Percentage, type = 'bar', color = ~Race) %>%
layout(title = paste("Racial Demographics -", input$neighborhood),
xaxis = list(title = "Race"),
yaxis = list(title = "Percentage", range = c(0, 100), tickvals = seq(0, 100, 20)))
})
}
```
</br></br></details>
<details markdown='1'><summary>Show Tab 2 Server Code </summary>
```{r eval=FALSE}
tab2_server <- function(input, output, session) {
# Map output for Socioeconomic Demographics
output$map_socio <- renderLeaflet({
valid_data <- nyc_data[!is.na(nyc_data[[input$color_socio]]), ]
pal <- colorQuantile("viridis", valid_data[[input$color_socio]], n = 5)
leaflet(valid_data) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addPolygons(
fillColor = ~pal(valid_data[[input$color_socio]]),
fillOpacity = 0.7, weight = 1, color = "white",
popup = ~paste(NTAName, "<br>",
paste(input$color_socio, ":", round(valid_data[[input$color_socio]], 2), "%"))
) %>%
addLegend(
position = "bottomright",
pal = pal,
values = valid_data[[input$color_socio]],
title = "% of population"
) %>%
setView(lng = -73.935242, lat = 40.730610, zoom = 10)
})
# Socioeconomic Demographics chart
output$socioDemoChart <- renderPlotly({
chart_data <- nyc_data_df[nyc_data_df$NTAName == input$neighborhood_socio, ]
# Extract data and remove "geometry" column
socio_data <- st_drop_geometry(chart_data)
socio_data <- socio_data[, c("pctpov", "rent.30", "rent.50")]
socio_data <- t(socio_data)
socio_data <- as.data.frame(socio_data)
socio_data <- cbind(Category = rownames(socio_data), Percentage = socio_data[, 1])
rownames(socio_data) <- NULL
plot_ly(data = as.data.frame(socio_data), x = ~Category, y = ~Percentage, type = 'bar', color = ~Category) %>%
layout(title = paste("Socioeconomic Demographics -", input$neighborhood_socio),
xaxis = list(title = "Category"),
yaxis = list(title = "Percentage", range = c(0, 100), tickvals = seq(0, 100, 20)))
})
}
```
</br></br></details>
<details markdown='1'><summary>Show Tab 3 Server Code </summary>
```{r eval=FALSE}
tab3_server <- function(input, output, session) {
# Map output for Health Demographics
output$map_health <- renderLeaflet({
valid_data <- nyc_data[!is.na(nyc_data[[input$color_health]]), ]
pal <- colorBin("viridis", valid_data[[input$color_health]], pretty = FALSE, n = 5)
leaflet(valid_data) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addPolygons(
fillColor = ~pal(valid_data[[input$color_health]]),
fillOpacity = 0.7, weight = 1, color = "white",
popup = ~paste(NTAName, "<br>",
paste(input$color_health, ":", round(valid_data[[input$color_health]], 2)))
) %>%
addLegend(
position = "bottomright",
pal = pal,
values = valid_data[[input$color_health]],
title = "Rate per X,000 persons"
) %>%
setView(lng = -73.935242, lat = 40.730610, zoom = 10)
})
# Health Demographics scatter plot
output$healthScatterChart <- renderPlotly({
plot_ly(nyc_data_df, x = ~smmrate, y = ~ptbrate, text = ~NTAName, type = 'scatter') %>%
layout(title = "Severe Maternal Morbidity vs Preterm Birth Rates",
xaxis = list(title = "Severe Maternal Morbidity Rate", zeroline = TRUE),
yaxis = list(title = "Preterm Birth Rate", zeroline = TRUE))
})
}
```
</br></br></details>
The goal in the third tab is to generate an interactive scatter plot to exist alongside the choropleth map selection. Here's a preview of what the final application page will look like:
![](images/06-shiny-tab3.png)
#### Run the Application
Combine all tabs into a single UI object. In a final step, we combine the server logic for all tabs and define the overall UI layout to launch the application.
<details markdown='1'><summary>Show UI Summary Code </summary>
```{r eval=FALSE}
ui <- fluidPage(
titlePanel("NYC Neighborhood Demographics"),
tabsetPanel(