source: trunk/grails-app/controllers/dbnp/visualization/VisualizeController.groovy @ 1997

Last change on this file since 1997 was 1997, checked in by taco@…, 8 years ago

VisualizeController?.groovy, added optional infoMessage in returnobjects. Working on determining visualizationtypes

File size: 30.9 KB
Line 
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 */
15package dbnp.visualization
16
17import dbnp.studycapturing.*;
18import grails.converters.JSON
19import groovy.lang.Closure;
20
21import org.dbnp.gdt.*
22
23class VisualizeController {
24        def authenticationService
25        def moduleCommunicationService
26    def infoMessage = ""
27
28        /**
29         * Shows the visualization screen
30         */
31        def index = {
32                [ studies: Study.giveReadableStudies( authenticationService.getLoggedInUser() )]
33        }
34
35        def getStudies = {
36                def studies = Study.giveReadableStudies( authenticationService.getLoggedInUser() );
37        returnResults(studies)
38        }
39
40        def getFields = {
41                def input_object
42                def studies
43
44                try{
45            // TODO: fix this
46                        input_object = JSON.parse(params.get('data'))
47                        studies = input_object.get('studies').id
48                } catch(Exception e) {
49                        returnError(400, "An error occured while retrieving the user input.")
50            log.error("VisualizationController: getFields: "+e)
51                }
52
53                def fields = [];
54                studies.each {
55                        /*
56                         Gather fields related to this study from GSCF.
57                         This requires:
58                         - a study.
59                         - a category variable, e.g. "events".
60                         - a type variable, either "domainfields" or "templatefields".
61                         */
62                        def study = Study.get(it)
63                        fields += getFields(study, "subjects", "domainfields")
64                        fields += getFields(study, "subjects", "templatefields")
65                        fields += getFields(study, "events", "domainfields")
66                        fields += getFields(study, "events", "templatefields")
67                        fields += getFields(study, "samplingEvents", "domainfields")
68                        fields += getFields(study, "samplingEvents", "templatefields")
69                        fields += getFields(study, "assays", "domainfields")
70                        fields += getFields(study, "assays", "templatefields")
71                        fields += getFields(study, "samples", "domainfields")
72                        fields += getFields(study, "samples", "domainfields")
73
74            /*
75            Gather fields related to this study from modules.
76            This will use the getMeasurements RESTful service. That service returns measurement types, AKA features.
77            It does not actually return measurements (the getMeasurementData call does).
78            The getFields method (or rather, the getMeasurements service) requires one or more assays and will return all measurement
79            types related to these assays.
80            So, the required variables for such a call are:
81              - a source variable, which can be obtained from AssayModule.list() (use the 'name' field)
82              - an assay, which can be obtained with study.getAssays()
83             */
84
85            study.getAssays().each { assay ->
86                def list = []
87                list = getFields(assay.module.id, assay)
88                if(list!=null){
89                    if(list.size()!=0){
90                        fields += list
91
92                    }
93                }
94            }
95
96                        // TODO: Maybe we should add study's own fields
97                }
98
99                returnResults(fields)
100        }
101
102        def getVisualizationTypes = {
103        def inputData = parseGetDataParams();
104        println "inputData: "+inputData
105
106
107        // TODO: handle the case of multiple fields on an axis
108        println "Checking type of row data"
109        def rowType = determineFieldType(inputData.studyIds[0], inputData.rowIds[0])
110        println "Checking type of column data"
111        def columnType = determineFieldType(inputData.studyIds[0], inputData.columnIds[0])
112
113        println "getVisualizationTypes: row contains data of type "+rowType+" and column contains data of type "+columnType
114
115                def types = [ [ "id": "barchart", "name": "Barchart"] ];
116                returnResults(types)
117        }
118
119    def getFields(source, assay){
120        /*
121        Gather fields related to this study from modules.
122        This will use the getMeasurements RESTful service. That service returns measurement types, AKA features.
123        getMeasurements does not actually return measurements (the getMeasurementData call does).
124        The getFields method (or rather, the getMeasurements service) requires one or more assays and will return all measurement
125        types related to these assays.
126        So, the required variables for such a call are:
127          - a source variable, which can be obtained from AssayModule.list() (use the 'name' field)
128          - a list of assays, which can be obtained with study.getAssays()
129
130        Output is a list of items. Each item contains
131          - an 'id'
132          - a 'source', which is a module identifier
133          - a 'category', which indicates where the field can be found. When dealing with module data as we are here, this is the assay name(s)
134          - a 'name', which is the the name of the field
135         */
136        def fields = []
137        def callUrl = ""
138
139        // Making a different call for each assay
140        // TODO: Change this to one call that requests fields for all assays, when you get that to work (in all cases)
141
142        def urlVars = "assayToken="+assay.assayUUID
143        try {
144            callUrl = ""+assay.module.url + "/rest/getMeasurements/query?"+urlVars
145            def json = moduleCommunicationService.callModuleRestMethodJSON( assay.module.url /* consumer */, callUrl );
146            def collection = []
147            json.each{ jason ->
148                collection.add(jason)
149            }
150            // Formatting the data
151            collection.each { field ->
152                // For getting this field from this assay
153                fields << [ "id": createFieldId( id: field, name: field, source: assay.id, type: ""+assay.name), "source": source, "category": ""+assay.name, "name": field ]
154            }
155        } catch(Exception e){
156            returnError(404, "An error occured while trying to collect field data from a module. Most likely, this module is offline.")
157            log.error("VisualizationController: getFields: "+e)
158        }
159
160        return fields
161    }
162
163   def getFields(study, category, type){
164        /*
165        Gather fields related to this study from GSCF.
166        This requires:
167          - a study.
168          - a category variable, e.g. "events".
169          - a type variable, either "domainfields" or "templatefields".
170
171        Output is a list of items, which are formatted by the formatGSCFFields function.
172        */
173
174        // Collecting the data from it's source
175        def collection
176        def fields = []
177        def source = "GSCF"
178
179        // Gathering the data
180        if(category=="subjects"){
181            if(type=="domainfields"){
182                collection = Subject.giveDomainFields()
183            }
184            if(type=="templatefields"){
185                collection = study?.samples?.parentSubject?.template?.fields
186            }
187        }
188        if(category=="events"){
189            if(type=="domainfields"){
190                collection = Event.giveDomainFields()
191            }
192            if(type=="templatefields"){
193                collection = study?.samples?.parentEventGroup?.events?.template?.fields
194            }
195        }
196        if(category=="samplingEvents"){
197            if(type=="domainfields"){
198                collection = SamplingEvent.giveDomainFields()
199            }
200            if(type=="templatefields"){
201                collection = study?.samples?.parentEventGroup?.samplingEvents?.template?.fields
202            }
203        }
204        if(category=="samples"){
205            if(type=="domainfields"){
206                collection = Sample.giveDomainFields()
207            }
208            if(type=="templatefields"){
209                collection = study?.samples?.template?.fields
210            }
211        }
212        if(category=="assays"){
213            if(type=="domainfields"){
214                collection = Assay.giveDomainFields()
215            }
216            if(type=="templatefields"){
217                collection = study?.assays?.template?.fields
218            }
219        }
220
221        collection.unique()
222
223        // Formatting the data
224        fields += formatGSCFFields(type, collection, source, category)
225
226        return fields
227    }
228
229    def formatGSCFFields(type, inputObject, source, category){
230        /*  The formatGSCFFields function can receive both lists of fields and single fields. If it receives a list, it calls itself again for each item in the list. This way, the original formatGSCFFields call will return single fields regardless of it's input.
231
232        Output is a list of items. Each item contains
233          - an 'id'
234          - a 'source', which in this case will be "GSCF"
235          - a 'category', which indicates where the field can be found, e.g. "subjects", "samplingEvents"
236          - a 'name', which is the the name of the field
237         */
238        if(inputObject==null || inputObject == []){
239            return []
240        }
241        def fields = []
242        if(inputObject instanceof Collection){
243            // Apparently this field is actually a list of fields.
244            // We will call ourselves again with the list's elements as input.
245            // These list elements will themselves go through this check again, effectively flattening the original input
246            for(int i = 0; i < inputObject.size(); i++){
247                fields += formatGSCFFields(type, inputObject[i], source, category)
248            }
249            return fields
250        } else {
251            // This is a single field. Format it and return the result.
252            if(type=="domainfields"){
253                fields << [ "id": createFieldId( id: inputObject.name, name: inputObject.name, source: source, type: category ), "source": source, "category": category, "name": inputObject.name ]
254            }
255            if(type=="templatefields"){
256                fields << [ "id": createFieldId( id: inputObject.id, name: inputObject.name, source: source, type: category ), "source": source, "category": category, "name": inputObject.name ]
257            }
258            return fields
259        }
260    }
261
262        /**
263         * Retrieves data for the visualization itself.
264         */
265        def getData = {
266                // Extract parameters
267                // TODO: handle erroneous input data
268                def inputData = parseGetDataParams();
269               
270                // TODO: handle the case that we have multiple studies
271                def studyId = inputData.studyIds[ 0 ];
272                def study = Study.get( studyId as Integer );
273
274                // Find out what samples are involved
275                def samples = study.samples
276
277                // Retrieve the data for both axes for all samples
278                // TODO: handle the case of multiple fields on an axis
279                def fields = [ "x": inputData.columnIds[ 0 ], "y": inputData.rowIds[ 0 ] ];
280                def data = getAllFieldData( study, samples, fields );
281
282                // Group data based on the y-axis if categorical axis is selected
283                // TODO: handle categories and continuous data
284                def groupedData = groupFieldData( data );
285
286                // Format data so it can be rendered as JSON
287                def returnData = formatData( groupedData, fields );
288
289                returnResults(returnData)
290        }
291
292        /**
293         * Parses the parameters given by the user into a proper list
294         * @return Map with 4 keys:
295         *              studyIds:       list with studyIds selected
296         *              rowIds:         list with fieldIds selected for the rows
297         *              columnIds:      list with fieldIds selected for the columns
298         *              visualizationType:      String with the type of visualization required
299         * @see getFields
300         * @see getVisualizationTypes
301         */
302        def parseGetDataParams() {
303                def studyIds, rowIds, columnIds, visualizationType;
304               
305                def inputData = params.get( 'data' );
306                try{
307                        def input_object = JSON.parse(inputData)
308                       
309                        studyIds = input_object.get('studies')*.id
310                        rowIds = input_object.get('rows')*.id
311                        columnIds = input_object.get('columns')*.id
312                        visualizationType = "barchart"
313                } catch(Exception e) {
314                        returnError(400, "An error occured while retrieving the user input")
315                        log.error("VisualizationController: parseGetDataParams: "+e)
316                }
317
318                return [ "studyIds" : studyIds, "rowIds": rowIds, "columnIds": columnIds, "visualizationType": visualizationType ];
319        }
320
321        /**
322         * Retrieve the field data for the selected fields
323         * @param study         Study for which the data should be retrieved
324         * @param samples       Samples for which the data should be retrieved
325         * @param fields        Map with key-value pairs determining the name and fieldId to retrieve data for. Example:
326         *                                              [ "x": "field-id-1", "y": "field-id-3" ]
327         * @return                      A map with the same keys as the input fields. The values in the map are lists of values of the
328         *                                      selected field for all samples. If a value could not be retrieved for a sample, null is returned. Example:
329         *                                              [ "x": [ 3, 6, null, 10 ], "y": [ "male", "male", "female", "female" ] ]
330         */
331        def getAllFieldData( study, samples, fields ) {
332                def fieldData = [:]
333                fields.each{ field ->
334                        fieldData[ field.key ] = getFieldData( study, samples, field.value );
335                }
336               
337                return fieldData;
338        }
339       
340        /**
341        * Retrieve the field data for the selected field
342        * @param study          Study for which the data should be retrieved
343        * @param samples        Samples for which the data should be retrieved
344        * @param fieldId        ID of the field to return data for
345        * @return                       A list of values of the selected field for all samples. If a value
346        *                                       could not be retrieved for a sample, null is returned. Examples:
347        *                                               [ 3, 6, null, 10 ] or [ "male", "male", "female", "female" ]
348        */
349        def getFieldData( study, samples, fieldId ) {
350                // Parse the fieldId as given by the user
351                def parsedField = parseFieldId( fieldId );
352               
353                def data = []
354               
355                if( parsedField.source == "GSCF" ) {
356                        // Retrieve data from GSCF itself
357                        def closure = valueCallback( parsedField.type )
358                       
359                        if( closure ) {
360                                samples.each { sample ->
361                                        // Retrieve the value for the selected field for this sample
362                                        def value = closure( sample, parsedField.name );
363                                       
364                                        if( value ) {
365                                                data << value;
366                                        } else {
367                                                // Return null if the value is not found
368                                                data << null
369                                        }
370                                }
371                        } else {
372                                // TODO: Handle error properly
373                                // Closure could not be retrieved, probably because the type is incorrect
374                                data = samples.collect { return null }
375                log.error("VisualizationController: getFieldData: Requested wrong field type: "+parsedField.type+". Parsed field: "+parsedField)
376                        }
377                } else {
378                        // Data must be retrieved from a module
379                        data = getModuleData( study, samples, parsedField.source, parsedField.name );
380                }
381               
382                return data
383        }
384       
385        /**
386         * Retrieve data for a given field from a data module
387         * @param study                 Study to retrieve data for
388         * @param samples               Samples to retrieve data for
389         * @param source_module Name of the module to retrieve data from
390         * @param fieldName             Name of the measurement type to retrieve (i.e. measurementToken)
391         * @return                              A list of values of the selected field for all samples. If a value
392         *                                              could not be retrieved for a sample, null is returned. Examples:
393         *                                                      [ 3, 6, null, 10 ] or [ "male", "male", "female", "female" ]
394         */
395        def getModuleData( study, samples, source_module, fieldName ) {
396                def data = []
397               
398                // TODO: Handle values that should be retrieved from multiple assays
399                def assay = Assay.get(source_module);
400               
401                if( assay ) {
402                        // Request for a particular assay and a particular feature
403                        def urlVars = "assayToken=" + assay.assayUUID + "&measurementToken="+fieldName
404                        urlVars += "&" + samples.collect { "sampleToken=" + it.sampleUUID }.join( "&" );
405                       
406                        def callUrl
407                        try {
408                                callUrl = assay.module.url + "/rest/getMeasurementData"
409                                def json = moduleCommunicationService.callModuleMethod( assay.module.url, callUrl, urlVars, "POST" );
410                               
411                                if( json ) {
412                                        // First element contains sampletokens
413                                        // Second element contains the featurename
414                                        // Third element contains the measurement value
415                                        def sampleTokens = json[ 0 ]
416                                        def measurements = json[ 2 ]
417                                       
418                                        // Loop through the samples
419                                        samples.each { sample ->
420                                                // Search for this sampletoken
421                                                def sampleToken = sample.sampleUUID;
422                                                def index = sampleTokens.findIndexOf { it == sampleToken }
423                                               
424                                                if( index > -1 ) {
425                                                        data << measurements[ index ];
426                                                } else {
427                                                        data << null
428                                                }
429                                        }
430                                } else {
431                                        // TODO: handle error
432                                        // Returns an empty list with as many elements as there are samples
433                                        data = samples.collect { return null }
434                                }
435                               
436                        } catch(Exception e){
437                returnError(404, "An error occured while trying to collect data from a module. Most likely, this module is offline.")
438                log.error("VisualizationController: getFields: "+e)
439                        }
440                } else {
441                        // TODO: Handle error correctly
442                        // Returns an empty list with as many elements as there are samples
443                        data = samples.collect { return null }
444                }
445               
446                return data
447
448        }
449
450        /**
451         * Group the field data on the values of the specified axis. For example, for a bar chart, the values
452         * on the x-axis should be grouped. Currently, the values for each group are averaged, and the standard
453         * error of the mean is returned in the 'error' property
454         * @param data          Data for both group- and value axis. The output of getAllFieldData fits this input
455         * @param groupAxis     Name of the axis to group on. Defaults to "x"
456         * @param valueAxis     Name of the axis where the values are. Defaults to "y"
457         * @param errorName     Key in the output map where 'error' values (SEM) are stored. Defaults to "error"
458         * @param unknownName   Name of the group for all null groups. Defaults to "unknown"
459         * @return                      A map with the keys 'groupAxis', 'valueAxis' and 'errorName'. The values in the map are lists of values of the
460         *                                      selected field for all groups. For example, if the input is
461         *                                              [ "x": [ "male", "male", "female", "female", null, "female" ], "y": [ 3, 6, null, 10, 4, 5 ] ]
462         *                                      the output will be:
463         *                                              [ "x": [ "male", "female", "unknown" ], "y": [ 4.5, 7.5, 4 ], "error": [ 1.5, 2.5, 0 ] ]
464         *
465         *                                      As you can see: null values in the valueAxis are ignored. Null values in the
466         *                                      group axis are combined into a 'unknown' category.
467         */
468        def groupFieldData( data, groupAxis = "x", valueAxis = "y", errorName = "error", unknownName = "unknown" ) {
469                // Create a unique list of values in the groupAxis. First flatten the list, since it might be that a
470                // sample belongs to multiple groups. In that case, the group names should not be the lists, but the list
471                // elements. A few lines below, this case is handled again by checking whether a specific sample belongs
472                // to this group.
473                // After flattening, the list is uniqued. The closure makes sure that values with different classes are
474                // always treated as different items (e.g. "" should not equal 0, but it does if using the default comparator)
475                def groups = data[ groupAxis ]
476                                                .flatten()
477                                                .unique { it == null ? "null" : it.class.name + it.toString() }
478               
479                // Make sure the null category is last
480                groups = groups.findAll { it != null } + groups.findAll { it == null }
481               
482                // Gather names for the groups. Most of the times, the group names are just the names, only with
483                // a null value, the unknownName must be used
484                def groupNames = groups.collect { it != null ? it : unknownName }
485               
486                // Generate the output object
487                def outputData = [:]
488                outputData[ valueAxis ] = [];
489                outputData[ errorName ] = [];
490                outputData[ groupAxis ] = groupNames;
491               
492                // Loop through all groups, and gather the values for this group
493                groups.each { group ->
494                        // Find the indices of the samples that belong to this group. if a sample belongs to multiple groups (i.e. if
495                        // the samples groupAxis contains multiple values, is a collection), the value should be used in all groups.
496                        def indices= data[ groupAxis ].findIndexValues { it instanceof Collection ? it.contains( group ) : it == group };
497                        def values = data[ valueAxis ][ indices ]
498                       
499                        def dataForGroup = computeMeanAndError( values );
500                       
501                        outputData[ valueAxis ] << dataForGroup.value
502                        outputData[ errorName ] << dataForGroup.error 
503                }
504
505                return outputData
506        }
507       
508        /**
509         * Formats the grouped data in such a way that the clientside visualization method
510         * can handle the data correctly.
511         * @param groupedData   Data that has been grouped using the groupFields method
512         * @param fields                Map with key-value pairs determining the name and fieldId to retrieve data for. Example:
513         *                                                      [ "x": "field-id-1", "y": "field-id-3" ]
514         * @param groupAxis             Name of the axis to with group data. Defaults to "x"
515         * @param valueAxis             Name of the axis where the values are stored. Defaults to "y"
516         * @param errorName             Key in the output map where 'error' values (SEM) are stored. Defaults to "error"         *
517         * @return                              A map like the following:
518         *
519                        {
520                                "type": "barchart",
521                                "x": [ "Q1", "Q2", "Q3", "Q4" ],
522                                "xaxis": { "title": "quarter 2011", "unit": "" },
523                                "yaxis": { "title": "temperature", "unit": "degrees C" },
524                                "series": [
525                                        {
526                                                "name": "series name",
527                                                "y": [ 5.1, 3.1, 20.6, 15.4 ],
528                                                "error": [ 0.5, 0.2, 0.4, 0.5 ]
529                                        },
530                                ]
531                        }
532         *
533         */
534        def formatData( groupedData, fields, groupAxis = "x", valueAxis = "y", errorName = "error" ) {
535                // TODO: Handle name and unit of fields correctly
536               
537                def return_data = [:]
538                return_data[ "type" ] = "barchart"
539                return_data[ "x" ] = groupedData[ groupAxis ].collect { it.toString() }
540                return_data.put("yaxis", ["title" : parseFieldId( fields[ valueAxis ] ).name, "unit" : "" ])
541                return_data.put("xaxis", ["title" : parseFieldId( fields[ groupAxis ] ).name, "unit": "" ])
542                return_data.put("series", [[
543                        "name": "Y",
544                        "y": groupedData[ valueAxis ],
545                        "error": groupedData[ errorName ]
546                ]])
547               
548                return return_data;
549        }
550
551        /**
552         * Returns a closure for the given entitytype that determines the value for a criterion
553         * on the given object. The closure receives two parameters: the sample and a field.
554         *
555         * For example:
556         *              How can one retrieve the value for subject.name, given a sample? This can be done by
557         *              returning the field values sample.parentSubject:
558         *                      { sample, field -> return getFieldValue( sample.parentSubject, field ) }
559         * @return      Closure that retrieves the value for a field and the given field
560         */
561        protected Closure valueCallback( String entity ) {
562                switch( entity ) {
563                        case "Study":
564                        case "studies":
565                                return { sample, field -> return getFieldValue( sample.parent, field ) }
566                        case "Subject":
567                        case "subjects":
568                                return { sample, field -> return getFieldValue( sample.parentSubject, field ); }
569                        case "Sample":
570                        case "samples":
571                                return { sample, field -> return getFieldValue( sample, field ) }
572                        case "Event":
573                        case "events":
574                                return { sample, field ->
575                                        if( !sample || !sample.parentEventGroup || !sample.parentEventGroup.events || sample.parentEventGroup.events.size() == 0 )
576                                                return null
577
578                                        return sample.parentEventGroup.events?.collect { getFieldValue( it, field ) };
579                                }
580                        case "SamplingEvent":
581                        case "samplingEvents":
582                                return { sample, field -> return getFieldValue( sample.parentEvent, field ); }
583                        case "Assay":
584                        case "assays":
585                                return { sample, field ->
586                                        def sampleAssays = Assay.findByParent( sample.parent ).findAll { it.samples?.contains( sample ) };
587                                        if( sampleAssays && sampleAssays.size() > 0 )
588                                                return sampleAssays.collect { getFieldValue( it, field ) }
589                                        else
590                                                return null
591                                }
592                }
593        }
594       
595        /**
596         * Computes the mean value and Standard Error of the mean (SEM) for the given values
597         * @param values        List of values to compute the mean and SEM for. Strings and null
598         *                                      values are ignored
599         * @return                      Map with two keys: 'value' and 'error'
600         */
601        protected Map computeMeanAndError( values ) {
602                // TODO: Handle the case that one of the values is a list. In that case,
603                // all values should be taken into account.     
604                def mean = computeMean( values );
605                def error = computeSEM( values, mean );
606               
607                return [ 
608                        "value": mean,
609                        "error": error
610                ]
611        }
612       
613        /**
614         * Computes the mean of the given values. Values that can not be parsed to a number
615         * are ignored. If no values are given, the mean of 0 is returned.
616         * @param values        List of values to compute the mean for
617         * @return                      Arithmetic mean of the values
618         */
619        protected def computeMean( List values ) {
620                def sumOfValues = 0;
621                def sizeOfValues = 0;
622                values.each { value ->
623                        def num = getNumericValue( value );
624                        if( num != null ) {
625                                sumOfValues += num;
626                                sizeOfValues++
627                        } 
628                }
629
630                if( sizeOfValues > 0 )
631                        return sumOfValues / sizeOfValues;
632                else
633                        return 0;
634        }
635
636        /**
637        * Computes the standard error of mean of the given values. 
638        * Values that can not be parsed to a number are ignored. 
639        * If no values are given, the standard deviation of 0 is returned.
640        * @param values         List of values to compute the standard deviation for
641        * @param mean           Mean of the list (if already computed). If not given, the mean
642        *                                       will be computed using the computeMean method
643        * @return                       Standard error of the mean of the values or 0 if no values can be used.
644        */
645   protected def computeSEM( List values, def mean = null ) {
646           if( mean == null )
647                        mean = computeMean( values )
648           
649           def sumOfDifferences = 0;
650           def sizeOfValues = 0;
651           values.each { value ->
652                   def num = getNumericValue( value );
653                   if( num != null ) {
654                           sumOfDifferences += Math.pow( num - mean, 2 );
655                           sizeOfValues++
656                   }
657           }
658
659           if( sizeOfValues > 0 ) {
660                   def std = Math.sqrt( sumOfDifferences / sizeOfValues );
661                   return std / Math.sqrt( sizeOfValues );
662           } else {
663                   return 0;
664           }
665   }
666               
667        /**
668         * Return the numeric value of the given object, or null if no numeric value could be returned
669         * @param       value   Object to return the value for
670         * @return                      Number that represents the given value
671         */
672        protected Number getNumericValue( value ) {
673                // TODO: handle special types of values
674                if( value instanceof Number ) {
675                        return value;
676                } else if( value instanceof RelTime ) {
677                        return value.value;
678                }
679               
680                return null
681        }
682
683        /** 
684         * Returns a field for a given templateentity
685         * @param object        TemplateEntity (or subclass) to retrieve data for
686         * @param fieldName     Name of the field to return data for.
687         * @return                      Value of the field or null if the value could not be retrieved
688         */
689        protected def getFieldValue( TemplateEntity object, String fieldName ) {
690                if( !object || !fieldName )
691                        return null;
692               
693                try {
694                        return object.getFieldValue( fieldName );
695                } catch( Exception e ) {
696                        return null;
697                }
698        }
699
700        /**
701         * Parses a fieldId that has been created earlier by createFieldId
702         * @param fieldId       FieldId to parse
703         * @return                      Map with attributes of the selected field. Keys are 'name', 'id', 'source' and 'type'
704         * @see createFieldId
705         */
706        protected Map parseFieldId( String fieldId ) {
707                def attrs = [:]
708               
709                def parts = fieldId.split(",")
710               
711                attrs = [
712                        "id": parts[ 0 ],
713                        "name": parts[ 1 ],
714                        "source": parts[ 2 ],
715                        "type": parts[ 3 ]
716                ]
717        }
718       
719        /**
720         * Create a fieldId based on the given attributes
721         * @param attrs         Map of attributes for this field. Keys may be 'name', 'id', 'source' and 'type'
722         * @return                      Unique field ID for these parameters
723         * @see parseFieldId
724         */
725        protected String createFieldId( Map attrs ) {
726                // TODO: What if one of the attributes contains a comma?
727                def name = attrs.name;
728                def id = attrs.id ?: name;
729                def source = attrs.source;
730                def type = attrs.type ?: ""
731               
732                return id + "," + name + "," + source + "," + type;
733        }
734
735    protected void returnError(code, msg){
736        response.sendError(code , msg)
737    }
738
739    protected String determineFieldType(studyId, fieldId){
740        // Parse the fieldId as given by the user
741                def parsedField = parseFieldId( fieldId );
742
743        def study = Study.get(studyId)
744        println "study: "+study+", parsedField: "+parsedField
745
746                def data = []
747
748                if( parsedField.source == "GSCF" ) {
749            if(parsedField.id.isNumber()){
750                // Templatefield
751                // ask for tf by id, ask for .type
752                println "GSCF, dunno yet"
753            } else {
754                // Domainfield or memberclass
755                switch( parsedField.type ) {
756                    case "Study":
757                    case "studies":
758                        def cat = determineCategoryFromClass(Study[parsedField.name].class)
759                        println "parsedField.type: "+parsedField.type+", outcome: "+cat
760                        return cat
761                        break
762                    case "Subject":
763                    case "subjects":
764                        def cat = determineCategoryFromClass(Subject[parsedField.name].class)
765                        println "parsedField.type: "+parsedField.type+", outcome: "+cat
766                        return cat
767                        break
768                    case "Sample":
769                    case "samples":
770                        def cat = determineCategoryFromClass(Sample[parsedField.name].class)
771                        println "parsedField.type: "+parsedField.type+", outcome: "+cat
772                        return cat
773                        break
774                    case "Event":
775                    case "events":
776                        def cat = determineCategoryFromClass(Event[parsedField.name].class)
777                        println "parsedField.type: "+parsedField.type+", outcome: "+cat
778                        return cat
779                        break
780                    case "SamplingEvent":
781                    case "samplingEvents":
782                        def cat = determineCategoryFromClass(SamplingEvent[parsedField.name].class)
783                        println "parsedField.type: "+parsedField.type+", outcome: "+cat
784                        return cat
785                        break
786                    case "Assay":
787                    case "assays":
788                        def cat = determineCategoryFromClass(Assay[parsedField.name].class)
789                        println "parsedField.type: "+parsedField.type+", outcome: "+cat
790                        return cat
791                        break
792                }
793            }
794
795            // Check parsedField.id == number
796                } else {
797            data = getModuleData( study, study.getSamples(), parsedField.source, parsedField.name );
798            def cat = determineCategoryFromData(data)
799            return cat
800                }
801    }
802
803    protected String determineCategoryFromClass(inputObject){
804        println "determineCategoryFromClass: "+inputObject+", class: "+inputObject.class
805        if(inputObject==java.lang.String){
806            return "cat"
807            // TODO: make this a final
808        } else {
809            return "num"
810            // TODO: make this a final
811        }
812    }
813
814    protected String determineCategoryFromData(inputObject){
815        def results = []
816        if(inputObject instanceof Collection){
817            // More complex datatype, call outselves again
818            inputObject.each {
819                results << determineCategoryFromData(it)
820            }
821        } else {
822            if(inputObject.toString().isDouble()){
823                results << "num"
824                // TODO: make this a final
825            } else {
826                results << "cat"
827                // TODO: make this a final
828            }
829        }
830
831        results.unique()
832
833        if(results.size()>1){
834            //log.error("VisualizeController: determineCategoryFromData: Category list contains more than one category! List: "+results+", inputObject: "+inputObject)
835            results[0] = "cat"
836            // TODO: make this a final
837        }
838
839        return results[0]
840    }
841
842    protected void returnResults(returnData){
843        def results = [:]
844        if(infoMessage!=""){
845            results.put("infoMessage", returnData)
846        }
847        results.put("returnData", returnData)
848        render results as JSON
849    }
850}
Note: See TracBrowser for help on using the repository browser.