1
1
library(" shiny" )
2
+ library(" DT" )
2
3
library(" zeallot" )
3
- library(" GenotypeCheck" )
4
+ library(" leaflet" )
5
+ library(" rgdal" )
6
+ # source("import_data.R")
7
+ # library("GenotypeCheck")
4
8
5
9
# The standard locus names, mostly for faster testing
6
10
locus_names <- c(" G10L" , " G10L.1" , " Mu05" , " Mu05.1" , " Mu09" , " Mu09.1" , " Mu10" , " Mu10.1" ,
7
11
" Mu23" , " Mu23.1" , " Mu50" , " Mu50.1" , " Mu51" , " Mu51.1" , " Mu59" , " Mu59.1" )
8
12
13
+ # Define coordinates system to convert from SWEREF99 (which the data is in) to WGS84 to render with leaflet
14
+ SWEREF99 <- CRS(" +init=epsg:3006" )
15
+ RT90 <- CRS(" +init=epsg:3021" )
16
+ WGS84 <- CRS(" +init=epsg:4326" )
17
+ UTM32N <- CRS(" +init=epsg:32632" )
18
+
9
19
# Define UI
10
20
ui <- fluidPage(
11
21
@@ -16,21 +26,22 @@ ui <- fluidPage(
16
26
sidebarLayout(
17
27
18
28
# Sidebar panel for inputs
19
- sidebarPanel(
29
+ sidebarPanel(width = 3 ,
20
30
21
31
# Input: Select a file
22
32
fileInput(" file1" , " Choose CSV File" ,
23
33
multiple = FALSE ,
24
34
accept = c(" text/csv" ,
25
35
" text/comma-separated-values,text/plain" ,
26
- " .csv" )),
36
+ " .csv" , " .xls " , " .xlsx " , " .ods " )),
27
37
28
38
29
39
# Input: Checkbox if file has header
30
40
checkboxInput(" header" , " Header" , TRUE ),
31
41
32
42
tags $ hr(),
33
43
# Select allele mismatch value
44
+ h4(" TODO::: Show amUniqueProfile To User If a button is clicked" ),
34
45
textInput(inputId = " alleleMismatchValue" , label = " Allowed Allele-mismatch" , value = " 3" ),
35
46
36
47
h4(" Type the column name of the specified columns." ),
@@ -45,31 +56,61 @@ ui <- fluidPage(
45
56
textInput(inputId = " northColumnName" , label = " North Column" , value = " Nord" ),
46
57
textInput(inputId = " eastColumnName" , label = " East Column" , value = " Ost" ),
47
58
textInput(inputId = " genderColumnName" , label = " Gender Column" , value = " Kon" ),
48
- textInput(inputId = " presetIndColumnName" , label = " PresetIndividual Column" , value = " " ),
59
+ textInput(inputId = " presetIndColumnName" , label = " PresetIndividual Column" , value = " Individ " ),
49
60
50
61
# Select Locus Columns
51
62
textInput(inputId = " locusColumnNames" , label = " Locus Columns (separated by ',')" , value = paste0(locus_names , collapse = " " , sep = " ," )),
52
63
53
64
# Parse Data
54
65
actionButton(inputId = " groupIndividuals" , label = " GROUP INDIVIDUALS" ),
55
66
56
- tags $ hr(),
57
67
tags $ hr(),
58
68
59
- # Display some result data to the user, TODO: allow the user to handle these exceptations
69
+ # Display some result data to the user
60
70
textOutput(outputId = " amtMultipleMatches" ),
61
71
textOutput(outputId = " amtUnclassified" ),
62
72
63
- ),
73
+ div(h4( " Handle Multiple Matches " ) ),
64
74
65
- # Main panel for displaying outputs ----
66
- mainPanel(
75
+ textInput( inputId = " multipleMatchIndex " , label = " View Details (Index of Multiple Matched Sample): " , placeholder = " tex 2 " ),
76
+ DT :: dataTableOutput( " multipleMatchesTable " ),
67
77
68
- # Output: Data file ----
69
- tableOutput( " contents " )
78
+ # TODO:: Allow the user to handle these (similar to matching new data)
79
+ div(h4( " Handle Unclassified Samples " )),
70
80
71
- )
81
+ DT :: dataTableOutput( " unclassifiedTable " ),
72
82
83
+ ),
84
+
85
+ # Main panel for displaying outputs
86
+ mainPanel(
87
+ # Panel for handeling multiple matched data, will probably be similar to the panel for matching new data
88
+ conditionalPanel(" input.multipleMatchIndex != ''" ,
89
+ # Showing Multimatch data for SEP123123 <-- Example
90
+ h4(textOutput(" multiMatchDataFor" )),
91
+ h5(" The sample matched the following individuals: " ),
92
+ # Desired: map beside data, now it jumps down because of size, not that important
93
+ sidebarLayout(
94
+ sidebarPanel = sidebarPanel(width = 9 ,
95
+ # Render the ones that were similar
96
+ DT :: dataTableOutput(" multipleMatchedSingle" ),
97
+ ),
98
+ mainPanel = mainPanel(
99
+ # render the map for the user to have all data when deciding which individual to add it to
100
+ leafletOutput(outputId = " multiMatchMap" ),
101
+ ),
102
+ ),
103
+ # User choose and add to a group of samples/individual - information
104
+ h5(" If this ID is one of the listed above the sample will be added to that group of sample/individual, if not, the sample will create a new individual IF the new ID does not already exist, make sure it is unique if that is the desired action." ),
105
+ # Text box to type new id, either create new group or create a override id for every sample in that group
106
+ textInput(inputId = " multipleMatchFix" , label = " Set ID/Individual to group: " ),
107
+ actionButton(inputId = " multipleMatchFixConfirm" , label = " Confirm/Save to data" ),
108
+ tags $ hr(),
109
+ ),
110
+
111
+ # Output: Data file
112
+ DT :: dataTableOutput(" contents" )
113
+ )
73
114
)
74
115
)
75
116
@@ -83,54 +124,164 @@ server <- function(input, output, session) {
83
124
multiple_matches <- list (index = character ())
84
125
unclassified <- list (index = character (), multilocus = character ())
85
126
127
+ data <- list ()
128
+
86
129
# Run allelematch and all GenotypeChecks the surrounding code when the click of the button
87
130
observeEvent(input $ groupIndividuals , {
88
131
groupIndividuals()
132
+ update_output_preprocess_data()
89
133
})
90
134
91
135
groupIndividuals <- function () {
92
- # Render the table of all sample data
93
- output $ contents <- renderTable({
94
- req(input $ file1 )
136
+ req(input $ file1 )
95
137
96
- # Read the locus data from the ui
97
- locus_columns <- strsplit(input $ locusColumnNames , " ," )[[1 ]]
138
+ # Read the locus data from the ui
139
+ locus_columns <- strsplit(input $ locusColumnNames , " ," )[[1 ]]
98
140
99
- # Read all of columns for the additional data from the ui
100
- additional_data <<- list (date = input $ dateColumnName , north = input $ northColumnName , east = input $ eastColumnName , gender = input $ genderColumnName , preset_ind = input $ presetIndColumnName )
101
- # If the user does not specify column they get removed here to not have empty objects later that mess things up
102
- additional_data <<- additional_data [additional_data != " " ]
141
+ # Read all of columns for the additional data from the ui
142
+ additional_data <<- list (date = input $ dateColumnName , north = input $ northColumnName , east = input $ eastColumnName , gender = input $ genderColumnName , preset_ind = input $ presetIndColumnName )
143
+ # If the user does not specify column they get removed here to not have empty objects later that mess things up
144
+ additional_data <<- additional_data [additional_data != " " ]
103
145
104
- index_column <- input $ indexColumnName
146
+ index_column <- input $ indexColumnName
105
147
106
- # Convert the numbers if header is deselected (and we are handeling columnindexes instead of columnnames)
107
- if (! input $ header ) {
108
- locus_columns <- sapply(locus_columns , as.numeric )
109
- additional_data <<- sapply(additional_data , as.numeric )
110
- index_column <- as.numeric(index_column )
111
- }
148
+ # Convert the numbers if header is deselected (and we are handeling columnindexes instead of columnnames)
149
+ if (! input $ header ) {
150
+ locus_columns <- sapply(locus_columns , as.numeric )
151
+ additional_data <<- sapply(additional_data , as.numeric )
152
+ index_column <- as.numeric(index_column )
153
+ }
112
154
113
- # Unpack the different data returned by our wrapper of allelematch into temp variables
114
- c( search_data_temp , multiple_matches_temp , unclassified_temp ) %<- % create_search_data(input $ file1 $ datapath , index_column , additional_data , locus_columns , as.numeric(input $ alleleMismatchValue ))
155
+ # Unpack the different data returned by our wrapper of allelematch into temp variables
156
+ c( data_temp , search_data_temp , multiple_matches_temp , unclassified_temp ) %<- % create_search_data(input $ file1 $ datapath , index_column , additional_data , locus_columns , as.numeric(input $ alleleMismatchValue ))
115
157
116
- # Change the session (server) data from the temp data
117
- search_data <<- search_data_temp
118
- multiple_matches <<- multiple_matches_temp
119
- unclassified <<- unclassified_temp
158
+ # Change the session (server) data from the temp data
159
+ search_data <<- search_data_temp
160
+ multiple_matches <<- multiple_matches_temp
161
+ unclassified <<- unclassified_temp
162
+ data <<- data_temp
163
+ }
120
164
165
+ update_output_preprocess_data <- function () {
166
+ # Render the table of all sample data
167
+ output $ contents <- DT :: renderDataTable(options = list (pageLength = 50 , lengthMenu = c(10 , 25 , 50 , 100 , 250 )), filter = " top" ,
168
+ {
121
169
# Show the indexes, multilocus and individual id data to the user
122
- return ( search_data )
170
+ search_data
123
171
})
124
172
125
173
# Display the amount of problematic data. TODO: Allow user to handle this data and tell the program what to do
126
174
output $ amtMultipleMatches <- renderText(
127
175
paste0(" There were: " , length(multiple_matches ), " samples that matched multiple individuals." )
128
176
)
129
177
178
+ # Render only the ones that matched multiple so the user can choose one
179
+ output $ multipleMatchesTable <- DT :: renderDataTable({
180
+ number_indexes <- 1 : length(multiple_matches )
181
+ # If there are none, avoid an vector that look like c(1, 0)
182
+ if (length(multiple_matches ) == 0 ) {
183
+ number_indexes <- c()
184
+ }
185
+ # Add id for the user to choose and view the details for one
186
+ df <- data.frame (list (multipleMatchIndex = number_indexes ))
187
+ rownames(df ) <- multiple_matches
188
+ df
189
+ })
190
+
191
+ # Show amount of unclassified samples in text
130
192
output $ amtUnclassified <- renderText(
131
193
paste0(" There were: " , length(unclassified $ index ), " samples that were unclassified." )
132
194
)
133
195
}
196
+
197
+ # Observe when user types an index to view details
198
+ observeEvent(input $ multipleMatchIndex , {
199
+ # Make sure the data is generated/button pressed, and that the user did not delete the index
200
+ req(input $ multipleMatchIndex , input $ groupIndividuals )
201
+
202
+ # If the index is parsable, continue
203
+ if (! is.na(as.numeric(input $ multipleMatchIndex )) & as.numeric(input $ multipleMatchIndex ) < = length(multiple_matches )) {
204
+ # Figure out what "big" index we are handeling, SEP index
205
+ showing_index <- multiple_matches [[as.numeric(input $ multipleMatchIndex )]]
206
+
207
+ # Create a filter c(TRUE, TRUE, FALSE, FALSE, FALSE) to select only the ones that are in the group of samples that the multimatched sample was in
208
+ # Get the multiple instaces of the sample, here in different groups
209
+ search_data_filter <- search_data $ index == showing_index
210
+ # Get the id of the groups that the sample is a port of
211
+ ids <- get_id(search_data [search_data_filter ,])
212
+ # Expand the filter to include everything with those id:s aswell
213
+ search_data_filter <- search_data_filter | get_id(search_data ) %in% ids
214
+
215
+ # Show information to user, which sample (SEP index)
216
+ output $ multiMatchDataFor <- renderText(paste0(" Showing Data For " , showing_index ))
217
+
218
+ # Render all samples that are part of the process to choose
219
+ output $ multipleMatchedSingle <- DT :: renderDataTable({
220
+ search_data [search_data_filter ,]
221
+ })
222
+
223
+ # If the user have specified map coordinates, continue
224
+ if (! is.null(additional_data $ north ) & ! is.null(additional_data $ east )) {
225
+ # Extract the long and lat from the data
226
+ # The data have user defined names for the columns, hence the pull with the additional_data which is the link between the user
227
+ # defined column-names and to us known names (north, east etc)
228
+ coords <- list (lng = pull(data [search_data $ index [search_data_filter ],], additional_data $ east ),
229
+ lat = pull(data [search_data $ index [search_data_filter ],], additional_data $ north ))
230
+ # Create a spatialpointsdataframe with the coordinates, empty meta-data and the input GPS system
231
+ p1 <- SpatialPointsDataFrame(coords , data = data.frame (list (temp = rep(NA , length(search_data_filter [search_data_filter == TRUE ])))), proj4string = SWEREF99 )
232
+ # Convert to WGS84 to render to the map and extract the coordinates
233
+ p2 <- spTransform(p1 , WGS84 ) %> %
234
+ coordinates()
235
+
236
+ # Render to the map
237
+ output $ multiMatchMap <- renderLeaflet({
238
+ # Get the id:s for all points to be renderer
239
+ ids <- get_id(search_data [search_data_filter ,])
240
+ # Change the id for the ones that have multiple ids. They have the same location and will be placed on top of each other
241
+ ids [search_data $ index [search_data_filter ] %in% multiple_matches ] <- " Multiple"
242
+ # Read the dates for the relevant samples from the meta-data
243
+ dates <- pull(data [search_data $ index [search_data_filter ],], additional_data $ date )
244
+ # Create all labels with the information we want to display
245
+ label <- paste0(" Index: " , search_data $ index [search_data_filter ], " ID: " , ids , " Date: " , dates )
246
+
247
+ # Render the map with leaflet and att markers
248
+ leaflet() %> %
249
+ # Get the map from openstreetmap
250
+ addProviderTiles(provider = providers $ OpenStreetMap ,
251
+ options = providerTileOptions(noWrap = TRUE )) %> %
252
+ # Add popups (take alot of space but gives all information, can be closed) - uses the label with the information created earlier
253
+ addPopups(lng = p2 [," lng" ], lat = p2 [," lat" ], popup = label , options = popupOptions(closeButton = TRUE )) %> %
254
+ # Add markers that show the information both on click and on hover, cant disapear
255
+ addMarkers(lng = p2 [," lng" ], lat = p2 [," lat" ], label = label , popup = label )
256
+ })
257
+ }
258
+ }
259
+ })
260
+
261
+ # Observe if user clicks the button to change the override id of the sample in question (that have been multimatched)
262
+ observeEvent(input $ multipleMatchFixConfirm , {
263
+ # Make sure the user has written a new id
264
+ req(input $ multipleMatchFix )
265
+
266
+ # Figure out the search_data index, (SEP index)
267
+ showing_index <- multiple_matches [[as.numeric(input $ multipleMatchIndex )]]
268
+ # Remove the sample from the list of samples that have multiple matches, it has been corrected by the user
269
+ multiple_matches <<- multiple_matches [multiple_matches != showing_index ]
270
+ # Remove all duplicated of the specific sample, the order doesnt matter as override id is the important parameter for the id
271
+ # We can remove the one with the correct id and set the override id of another without problem
272
+ search_data <<- search_data [! (search_data $ index == showing_index & duplicated(search_data $ index )),]
273
+ # Set the sample (now no duplicates, only one left) override id to be the id specified by the user
274
+ search_data $ override_id [search_data $ index == showing_index ] <<- input $ multipleMatchFix
275
+ # Set every sample that is in the same group to have a override id, maybe not necessary but to ensure the order generated by
276
+ # allelelmatch doesnt change and would therefor place the user "controlled" one in a then different group
277
+ search_data $ override_id [get_id(search_data ) == input $ multipleMatchFix ] <<- input $ multipleMatchFix
278
+
279
+ # Update the visual information, the big table and the count of multimatches
280
+ update_output_preprocess_data()
281
+ # Reset the chosen multimatch index, the conditional panel will disapear until the user chooses a new sample that have been multimatched to handle
282
+ updateTextInput(session , " multipleMatchIndex" , value = " " )
283
+ })
134
284
}
135
285
286
+ # Create a shiny app
136
287
shinyApp(ui = ui , server = server )
0 commit comments