1 | /** |
---|
2 | * Visualize Controller |
---|
3 | * |
---|
4 | * This controller enables the user to visualize his data |
---|
5 | * |
---|
6 | * @author robert@thehyve.nl |
---|
7 | * @since 20110825 |
---|
8 | * @package dbnp.visualization |
---|
9 | * |
---|
10 | * Revision information: |
---|
11 | * $Rev$ |
---|
12 | * $Author$ |
---|
13 | * $Date$ |
---|
14 | */ |
---|
15 | package dbnp.visualization |
---|
16 | |
---|
17 | import dbnp.studycapturing.*; |
---|
18 | import grails.converters.JSON |
---|
19 | import org.dbnp.gdt.* |
---|
20 | |
---|
21 | class VisualizeController { |
---|
22 | def authenticationService |
---|
23 | def moduleCommunicationService |
---|
24 | |
---|
25 | /** |
---|
26 | * Shows the visualization screen |
---|
27 | */ |
---|
28 | def index = { |
---|
29 | [ studies: Study.giveReadableStudies( authenticationService.getLoggedInUser() )] |
---|
30 | } |
---|
31 | |
---|
32 | def getStudies = { |
---|
33 | def studies = Study.giveReadableStudies( authenticationService.getLoggedInUser() ); |
---|
34 | render studies as JSON |
---|
35 | } |
---|
36 | |
---|
37 | def getFields = { |
---|
38 | def input_object |
---|
39 | def studies |
---|
40 | |
---|
41 | try{ |
---|
42 | input_object = JSON.parse(params.get('data')) |
---|
43 | studies = input_object.get('studies').id |
---|
44 | } catch(Exception e) { |
---|
45 | // TODO: properly handle this exception |
---|
46 | println e |
---|
47 | } |
---|
48 | |
---|
49 | def fields = []; |
---|
50 | studies.each { |
---|
51 | /* |
---|
52 | Gather fields related to this study from GSCF. |
---|
53 | This requires: |
---|
54 | - a study. |
---|
55 | - a category variable, e.g. "events". |
---|
56 | - a type variable, either "domainfields" or "templatefields". |
---|
57 | */ |
---|
58 | def study = Study.get(it) |
---|
59 | fields += getFields(study, "subjects", "domainfields") |
---|
60 | fields += getFields(study, "subjects", "templatefields") |
---|
61 | fields += getFields(study, "events", "domainfields") |
---|
62 | fields += getFields(study, "events", "templatefields") |
---|
63 | fields += getFields(study, "samplingEvents", "domainfields") |
---|
64 | fields += getFields(study, "samplingEvents", "templatefields") |
---|
65 | fields += getFields(study, "assays", "domainfields") |
---|
66 | fields += getFields(study, "assays", "templatefields") |
---|
67 | fields += getFields(study, "samples", "domainfields") |
---|
68 | fields += getFields(study, "samples", "domainfields") |
---|
69 | |
---|
70 | |
---|
71 | /* |
---|
72 | Gather fields related to this study from modules. |
---|
73 | This will use the getMeasurements RESTful service. That service returns measurement types, AKA features. |
---|
74 | It does not actually return measurements (the getMeasurementData call does). |
---|
75 | The getFields method (or rather, the getMeasurements service) requires one or more assays and will return all measurement |
---|
76 | types related to these assays. |
---|
77 | So, the required variables for such a call are: |
---|
78 | - a source variable, which can be obtained from AssayModule.list() (use the 'name' field) |
---|
79 | - a list of assays, which can be obtained with study.getAssays() |
---|
80 | */ |
---|
81 | AssayModule.list().each { module -> |
---|
82 | def list = [] |
---|
83 | list = getFields(module.name, study.getAssays()) |
---|
84 | if(list!=null){ |
---|
85 | if(list.size()!=0){ |
---|
86 | fields += list |
---|
87 | } |
---|
88 | } |
---|
89 | } |
---|
90 | |
---|
91 | // TODO: Maybe we should add study's own fields |
---|
92 | } |
---|
93 | |
---|
94 | render fields as JSON |
---|
95 | } |
---|
96 | |
---|
97 | def getVisualizationTypes = { |
---|
98 | def types = [ [ "id": "barchart", "name": "Barchart"] ]; |
---|
99 | render types as JSON |
---|
100 | } |
---|
101 | |
---|
102 | def getData = { |
---|
103 | println params |
---|
104 | def input_object |
---|
105 | def studies |
---|
106 | def rows |
---|
107 | def columns |
---|
108 | def vizualisation_type |
---|
109 | |
---|
110 | try{ |
---|
111 | input_object = JSON.parse(params.get('data')) |
---|
112 | studies = input_object.get('studies') |
---|
113 | rows = input_object.get('rows') |
---|
114 | columns = input_object.get('columns') |
---|
115 | vizualisation_type = "barchart" |
---|
116 | } catch(Exception e) { |
---|
117 | // TODO: properly handle this exception |
---|
118 | println e |
---|
119 | } |
---|
120 | |
---|
121 | def data = [:] |
---|
122 | |
---|
123 | Collection row_data = [] |
---|
124 | Collection column_data = [] |
---|
125 | studies.each { |
---|
126 | // TODO: Get rid of code duplication |
---|
127 | def study = Study.get(it.id) |
---|
128 | rows.eachWithIndex { r, index -> |
---|
129 | println " - field "+r |
---|
130 | def case_switch |
---|
131 | def input_id = r.id.split(",") |
---|
132 | def field_id = input_id[0] |
---|
133 | def source_module = input_id[1] |
---|
134 | def field_type = input_id[2] |
---|
135 | def field_name = input_id[3] |
---|
136 | def templatefield_source |
---|
137 | if(source_module=="GSCF"){ |
---|
138 | if(field_type!=TemplateField.class.toString()){ |
---|
139 | case_switch = "domain" |
---|
140 | } else { |
---|
141 | templatefield_source = input_id[4] |
---|
142 | } |
---|
143 | row_data[index] = getFieldData(study, case_switch, field_id, field_name, source_module, templatefield_source, field_type) |
---|
144 | } else { |
---|
145 | // Grabbing field data from a module |
---|
146 | row_data[index] = getFieldData(study, "", field_id, "", source_module, "", "") |
---|
147 | } |
---|
148 | } |
---|
149 | |
---|
150 | columns.eachWithIndex { r, index -> |
---|
151 | def case_switch |
---|
152 | def input_id = r.id.split(",") |
---|
153 | def field_id = input_id[0] |
---|
154 | def source_module = input_id[1] |
---|
155 | def field_type = input_id[2] |
---|
156 | def field_name = input_id[3] |
---|
157 | def templatefield_source |
---|
158 | |
---|
159 | if(source_module=="GSCF"){ |
---|
160 | if(field_type!=TemplateField.class.toString()){ |
---|
161 | case_switch = "domain" |
---|
162 | } else { |
---|
163 | templatefield_source = input_id[4] |
---|
164 | } |
---|
165 | column_data[index] = getFieldData(study, case_switch, field_id, field_name, source_module, templatefield_source, field_type) |
---|
166 | } else { |
---|
167 | // Grabbing field data from a module |
---|
168 | column_data[index] = getFieldData(study, "", field_id, "", source_module, "", "") |
---|
169 | } |
---|
170 | } |
---|
171 | } |
---|
172 | |
---|
173 | if(row_data.size()!=0 && column_data.size()!=0 && row_data[0].size()!=0 && column_data[0].size()!=0){ |
---|
174 | // Going to build the return object now |
---|
175 | def return_data = [:] |
---|
176 | def series = [] |
---|
177 | def possible_xaxis_title = "" |
---|
178 | def possible_yaxis_title = "" |
---|
179 | return_data.put("type", vizualisation_type) |
---|
180 | if(vizualisation_type=='barchart'){ |
---|
181 | |
---|
182 | // Determining what different bars we need (x-axis) |
---|
183 | def list_of_row_contents = [] |
---|
184 | rows.eachWithIndex { r, j -> |
---|
185 | row_data[j].each { datapoint -> |
---|
186 | list_of_row_contents.add(datapoint) |
---|
187 | } |
---|
188 | } |
---|
189 | def bars = [] |
---|
190 | // Make the list unique and stringify the individual objects |
---|
191 | list_of_row_contents.unique().each { |
---|
192 | item -> |
---|
193 | bars << item.toString() |
---|
194 | } |
---|
195 | bars.sort() |
---|
196 | return_data.put("x", bars) |
---|
197 | // Determining what different bars we need (x-axis) |
---|
198 | |
---|
199 | // Determine the different categories that datapoints can fall under |
---|
200 | def categories = [] |
---|
201 | columns.eachWithIndex { c, i -> |
---|
202 | column_data[i].each { |
---|
203 | cd -> |
---|
204 | categories << cd |
---|
205 | } |
---|
206 | } |
---|
207 | categories.unique().sort() |
---|
208 | |
---|
209 | // Looking at the actual datapoints ... |
---|
210 | columns.eachWithIndex { column, column_index -> |
---|
211 | // ... for each column |
---|
212 | categories.each { category -> |
---|
213 | def data_per_bar = [:] // To store the datapoints contained in the current category, ordered by bar |
---|
214 | // Make an entry for each of the bars that will be in the barchart |
---|
215 | list_of_row_contents.each { bar -> |
---|
216 | data_per_bar.put(bar, 0) |
---|
217 | } |
---|
218 | rows.eachWithIndex { row, row_index -> |
---|
219 | // ... check for each row what it contains for each bar and category combination |
---|
220 | // TODO: properly determine axis titles |
---|
221 | if(possible_xaxis_title==""){ |
---|
222 | possible_xaxis_title = row.id.split(',')[3] |
---|
223 | } |
---|
224 | list_of_row_contents.each { bar -> |
---|
225 | // Check for the current bar, how many datapoints each category has |
---|
226 | column_data[column_index].eachWithIndex { cd, cdi -> |
---|
227 | if(bar==row_data[row_index][cdi] && cd==category){ |
---|
228 | // Apparently this column contains a datapoint, whose entry in the relevant row equals the bar we are currently looking for and whose column equals the category that we are currently checking for. What this means is that the current datapoint should be included in this series |
---|
229 | data_per_bar.put(bar, data_per_bar.get(bar)+1) |
---|
230 | } |
---|
231 | } |
---|
232 | } |
---|
233 | } |
---|
234 | // Now add the data in the correct order (corresponding to bar order, so that the values end up in the right bar) |
---|
235 | def data_per_bar_sorted = [] |
---|
236 | list_of_row_contents.each { bar -> |
---|
237 | data_per_bar_sorted.add(data_per_bar.get(bar)) |
---|
238 | } |
---|
239 | series.add(["name":category.toString(),"y":data_per_bar_sorted]) |
---|
240 | } |
---|
241 | } |
---|
242 | |
---|
243 | if(possible_yaxis_title==""){ |
---|
244 | // TODO: properly determine axis titles |
---|
245 | possible_yaxis_title = "Amount" |
---|
246 | if(possible_xaxis_title!=""){ |
---|
247 | possible_yaxis_title += " of each "+possible_xaxis_title |
---|
248 | } |
---|
249 | } |
---|
250 | return_data.put("yaxis", ["title" : possible_yaxis_title, "unit" : "..."]) |
---|
251 | return_data.put("xaxis", ["title" : possible_xaxis_title, "unit": "..."]) |
---|
252 | return_data.put("series", series) |
---|
253 | data = return_data |
---|
254 | } |
---|
255 | } else { |
---|
256 | // TODO: handle this exception properly |
---|
257 | // We couldn't get any data to display... |
---|
258 | } |
---|
259 | println "\n\nReturn object: "+(data as JSON)+" ... " |
---|
260 | render data as JSON |
---|
261 | } |
---|
262 | |
---|
263 | def getFieldData(study, case_switch, field_id, field_name, source, templatefield_source, field_type){ |
---|
264 | if(source=="GSCF"){ |
---|
265 | if(case_switch=="domain"){ |
---|
266 | if(!study.getProperty(field_type)){ |
---|
267 | // TODO: handle this exception properly |
---|
268 | println "getFieldData: domainfield: Requested property '"+field_type+"' does not appear to exist in the study '"+study+"'." |
---|
269 | return |
---|
270 | } |
---|
271 | def domain_objects = study.getProperty(field_type) // Simple way of getting at the relevant domain objects |
---|
272 | |
---|
273 | if(domain_objects==null){ |
---|
274 | // TODO: handle this exception properly |
---|
275 | println "getFieldData: domainfield: A problem occurred... Nothing was collected." |
---|
276 | } |
---|
277 | |
---|
278 | // Get the value of the requested field out of the domain objects |
---|
279 | def dat = [] |
---|
280 | domain_objects.each{ |
---|
281 | try{ |
---|
282 | dat.add(it.getFieldValue(field_name)) |
---|
283 | //println "getFieldData: domainfield: *** It appears as though we were successful" |
---|
284 | } catch(Exception e){ |
---|
285 | // TODO: handle this exception properly |
---|
286 | println "getFieldData: domainfield: A problem occurred... "+e |
---|
287 | } |
---|
288 | } |
---|
289 | return dat |
---|
290 | } else { |
---|
291 | TemplateField tf |
---|
292 | try{ |
---|
293 | tf = TemplateField.get(field_id) |
---|
294 | } catch (Exception e){ |
---|
295 | // TODO: handle this exception properly |
---|
296 | println "getFieldData: templatefield: A problem occurred... "+e |
---|
297 | } |
---|
298 | def dat = [] |
---|
299 | def collection |
---|
300 | if(templatefield_source=="subjects"){ |
---|
301 | collection = study.getSubjects() |
---|
302 | } |
---|
303 | if(templatefield_source=="assays"){ |
---|
304 | collection = study.getAssays() |
---|
305 | } |
---|
306 | if(templatefield_source=="events"){ |
---|
307 | collection = study.getEvents() |
---|
308 | } |
---|
309 | if(templatefield_source=="samplingEvents"){ |
---|
310 | collection = study.getSamplingEvents() |
---|
311 | } |
---|
312 | if(templatefield_source=="samples"){ |
---|
313 | collection = study.getSamples() |
---|
314 | } |
---|
315 | if(collection==null){ |
---|
316 | // TODO: handle this exception properly |
---|
317 | println "getFieldData: templatefield: A problem occurred... Nothing was collected." |
---|
318 | } |
---|
319 | collection.each { |
---|
320 | try{ |
---|
321 | dat.add(it.getFieldValue(tf.name)) |
---|
322 | } catch(Exception e){ |
---|
323 | // TODO: handle this exception properly |
---|
324 | println "getFieldData: templatefield: A problem occurred... "+e |
---|
325 | } |
---|
326 | } |
---|
327 | return dat |
---|
328 | } |
---|
329 | } else { |
---|
330 | // Request for module data |
---|
331 | def dat = [] |
---|
332 | |
---|
333 | // User requested a particular feature |
---|
334 | study.getAssays().each { assay -> |
---|
335 | // Request for a particular assay and a particular feature |
---|
336 | def urlVars = "assayToken="+assay.assayUUID+"&measurementToken="+field_id |
---|
337 | def callUrl |
---|
338 | AssayModule.list().each { module -> |
---|
339 | if(source==module.name){ |
---|
340 | try { |
---|
341 | callUrl = module.url + "/rest/getMeasurementData/query?"+urlVars |
---|
342 | def json = moduleCommunicationService.callModuleRestMethodJSON( module.url, callUrl ); |
---|
343 | // First element contains sampletokens |
---|
344 | // Second element contains the featurename |
---|
345 | // Third element contains the measurement value |
---|
346 | // NOTE: There is no need to couple a measurement value to a sampletoken, because that just doesn't produce interesting data |
---|
347 | json[2].each { val -> |
---|
348 | dat << val |
---|
349 | } |
---|
350 | } catch(Exception e){ |
---|
351 | // TODO: handle this exception properly |
---|
352 | println "No success with\n\t"+callUrl+"\n"+e |
---|
353 | return null |
---|
354 | } |
---|
355 | } |
---|
356 | } |
---|
357 | } |
---|
358 | return dat |
---|
359 | } |
---|
360 | } |
---|
361 | |
---|
362 | def getFields(source, assays){ |
---|
363 | /* |
---|
364 | Gather fields related to this study from modules. |
---|
365 | This will use the getMeasurements RESTful service. That service returns measurement types, AKA features. |
---|
366 | It does not actually return measurements (the getMeasurementData call does). |
---|
367 | The getFields method (or rather, the getMeasurements service) requires one or more assays and will return all measurement |
---|
368 | types related to these assays. |
---|
369 | So, the required variables for such a call are: |
---|
370 | - a source variable, which can be obtained from AssayModule.list() (use the 'name' field) |
---|
371 | - a list of assays, which can be obtained with study.getAssays() |
---|
372 | */ |
---|
373 | def collection = [] |
---|
374 | def callUrl = "" |
---|
375 | |
---|
376 | // Making a different call for each assay |
---|
377 | // TODO: Change this to one call that requests fields for all assays, when you get that to work (in all cases) |
---|
378 | assays.each { assay -> |
---|
379 | def urlVars = "assayToken="+assay.assayUUID |
---|
380 | AssayModule.list().each { module -> |
---|
381 | if(source==module.name){ |
---|
382 | try { |
---|
383 | callUrl = module.url + "/rest/getMeasurements/query?"+urlVars |
---|
384 | def json = moduleCommunicationService.callModuleRestMethodJSON( module.url, callUrl ); |
---|
385 | json.each{ jason -> |
---|
386 | collection.add(jason) |
---|
387 | } |
---|
388 | } catch(Exception e){ |
---|
389 | // Todo: properly handle this exception |
---|
390 | println "No success with\n\t"+callUrl+"\n"+e |
---|
391 | return null |
---|
392 | } |
---|
393 | } |
---|
394 | } |
---|
395 | } |
---|
396 | |
---|
397 | def fields = [] |
---|
398 | // Formatting the data |
---|
399 | collection.each { field -> |
---|
400 | fields << [ "id": field+","+source+","+"feature"+","+field, "source": source, "category": "feature", "name": source+" feature "+field ] |
---|
401 | } |
---|
402 | return fields |
---|
403 | } |
---|
404 | |
---|
405 | def getFields(study, category, type){ |
---|
406 | /* |
---|
407 | Gather fields related to this study from GSCF. |
---|
408 | This requires: |
---|
409 | - a study. |
---|
410 | - a category variable, e.g. "events". |
---|
411 | - a type variable, either "domainfields" or "templatefields". |
---|
412 | */ |
---|
413 | |
---|
414 | // Collecting the data from it's source |
---|
415 | def collection |
---|
416 | def fields = [] |
---|
417 | def source = "GSCF" |
---|
418 | |
---|
419 | // Gathering the data |
---|
420 | if(category=="subjects"){ |
---|
421 | if(type=="domainfields"){ |
---|
422 | collection = Subject.giveDomainFields() |
---|
423 | } |
---|
424 | if(type=="templatefields"){ |
---|
425 | collection = study.giveSubjectTemplates().fields |
---|
426 | } |
---|
427 | } |
---|
428 | if(category=="events"){ |
---|
429 | if(type=="domainfields"){ |
---|
430 | collection = Event.giveDomainFields() |
---|
431 | } |
---|
432 | if(type=="templatefields"){ |
---|
433 | collection = study.giveEventTemplates().fields |
---|
434 | } |
---|
435 | } |
---|
436 | if(category=="samplingEvents"){ |
---|
437 | if(type=="domainfields"){ |
---|
438 | collection = SamplingEvent.giveDomainFields() |
---|
439 | } |
---|
440 | if(type=="templatefields"){ |
---|
441 | collection = study.giveSamplingEventTemplates().fields |
---|
442 | } |
---|
443 | } |
---|
444 | if(category=="samples"){ |
---|
445 | if(type=="domainfields"){ |
---|
446 | collection = Sample.giveDomainFields() |
---|
447 | } |
---|
448 | if(type=="templatefields"){ |
---|
449 | collection = study.giveEventTemplates().fields |
---|
450 | } |
---|
451 | } |
---|
452 | if(category=="assays"){ |
---|
453 | if(type=="domainfields"){ |
---|
454 | collection = Event.giveDomainFields() |
---|
455 | } |
---|
456 | if(type=="templatefields"){ |
---|
457 | collection = study.giveEventTemplates().fields |
---|
458 | } |
---|
459 | } |
---|
460 | |
---|
461 | // Formatting the data |
---|
462 | if(type=="domainfields"){ |
---|
463 | collection.each { field -> |
---|
464 | fields << [ "id": field.name+","+source+","+category+","+field.name, "source": source, "category": category, "name": category.capitalize()+" "+field.name ] |
---|
465 | } |
---|
466 | } |
---|
467 | if(type=="templatefields"){ |
---|
468 | collection.each { field -> |
---|
469 | for(int i = 0; i < field.size(); i++){ |
---|
470 | fields << [ "id": field[i].id+","+source+","+TemplateField.toString()+","+field[i].name+","+category, "source": source, "category": category, "name": category.capitalize()+" "+field[i].name ] |
---|
471 | } |
---|
472 | } |
---|
473 | } |
---|
474 | |
---|
475 | return fields |
---|
476 | } |
---|
477 | } |
---|