Show
Ignore:
Timestamp:
08-09-11 15:18:25 (3 years ago)
Author:
taco@…
Message:

Update for the visualization controller, primarily more commenting

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • trunk/grails-app/controllers/dbnp/visualization/VisualizeController.groovy

    r2002 r2009  
    2424        def moduleCommunicationService 
    2525    def infoMessage = "" 
    26     final int categoricalData = 0 
    27     final int numericalData = 1 
     26    final int CATEGORICALDATA = 0 
     27    final int NUMERICALDATA = 1 
    2828 
    2929        /** 
     
    3939        } 
    4040 
    41         def getFields = { 
     41        /** 
     42         * Based on the study id contained in the parameters given by the user, a list of 'fields' is returned. This list can be used to select what data should be visualized 
     43         * @return List containing fields 
     44     * @see parseGetDataParams 
     45         * @see getFields 
     46         */ 
     47    def getFields = { 
    4248                def input_object 
    4349                def studies 
     
    112118        } 
    113119 
     120        /** 
     121         * Based on the field ids contained in the parameters given by the user, a list of possible visualization types is returned. This list can be used to select how data should be visualized. 
     122         * @return List containing the possible visualization types, with each element containing 
     123     *          - a unique id 
     124     *          - a unique name 
     125     *         For example: ["id": "barchart", "name": "Barchart"] 
     126     * @see parseGetDataParams 
     127         * @see determineFieldType 
     128     * @see determineVisualizationTypes 
     129         */ 
    114130        def getVisualizationTypes = { 
    115131        def inputData = parseGetDataParams(); 
     
    130146 
    131147        // Determine possible visualization types 
    132         // TODO: Determine possible visualization types 
    133         def types = [] 
    134         if(rowType==categoricalData){ 
    135             if(columnType==categoricalData){ 
    136                 types = [ [ "id": "table", "name": "Table"] ]; 
    137             } 
    138             if(columnType==numericalData){ 
    139                 types = [ [ "id": "horizontal_barchart", "name": "Horizontal barchart"] ]; 
    140             } 
    141         } 
    142         if(rowType==numericalData){ 
    143             if(columnType==categoricalData){ 
    144                 types = [ [ "id": "barchart", "name": "Barchart"], [ "id": "linechart", "name": "Linechart"] ]; 
    145             } 
    146             if(columnType==numericalData){ 
    147                 types = [ [ "id": "scatterplot", "name": "Scatterplot"], [ "id": "linechart", "name": "Linechart"] ]; 
    148             } 
    149         } 
    150  
     148       def types = determineVisualizationTypes(rowType, columnType) 
     149 
     150        println "types: "+types 
    151151        return sendResults(types) 
    152152        } 
    153153 
    154     def getFields(source, assay){ 
    155         /* 
    156         Gather fields related to this study from modules. 
     154    /** 
     155     * Gather fields related to this study from modules. 
    157156        This will use the getMeasurements RESTful service. That service returns measurement types, AKA features. 
    158157        getMeasurements does not actually return measurements (the getMeasurementData call does). 
    159         The getFields method (or rather, the getMeasurements service) requires one or more assays and will return all measurement 
    160         types related to these assays. 
    161         So, the required variables for such a call are: 
    162           - a source variable, which can be obtained from AssayModule.list() (use the 'name' field) 
    163           - a list of assays, which can be obtained with study.getAssays() 
    164  
    165         Output is a list of items. Each item contains 
    166           - an 'id' 
    167           - a 'source', which is a module identifier 
    168           - 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) 
    169           - a 'name', which is the the name of the field 
    170          */ 
     158     * @param source    The id of the module that is the source of the requested fields, as can be obtained from AssayModule.list() (use the 'id' field) 
     159     * @param assay     The assay that the source module and the requested fields belong to 
     160     * @return  A list of map objects, containing the following: 
     161     *           - a key 'id' with a value formatted by the createFieldId function 
     162     *           - a key 'source' with a value equal to the input parameter 'source' 
     163     *           - a key 'category' with a value equal to the 'name' field of the input paramater 'assay' 
     164     *           - a key 'name' with a value equal to the name of the field in question, as determined by the source value 
     165     */ 
     166    def getFields(source, assay){ 
    171167        def fields = [] 
    172168        def callUrl = "" 
     
    195191    } 
    196192 
    197    def getFields(study, category, type){ 
    198         /* 
    199         Gather fields related to this study from GSCF. 
    200         This requires: 
    201           - a study. 
    202           - a category variable, e.g. "events". 
    203           - a type variable, either "domainfields" or "templatefields". 
    204  
    205         Output is a list of items, which are formatted by the formatGSCFFields function. 
    206         */ 
    207  
     193    /** 
     194     * Gather fields related to this study from GSCF. 
     195     * @param study The study that is the source of the requested fields 
     196     * @param category  The domain that a field (a property in this case) belongs to, e.g. "subjects", "samplingEvents" 
     197     * @param type A string that indicates the type of field, either "domainfields" or "templatefields". 
     198     * @return A list of map objects, formatted by the formatGSCFFields function 
     199     */ 
     200    def getFields(study, category, type){ 
    208201        // Collecting the data from it's source 
    209202        def collection = [] 
     
    240233    } 
    241234 
    242     def formatGSCFFields(type, inputObject, source, category){ 
    243         /*  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. 
    244  
    245         Output is a list of items. Each item contains 
    246           - an 'id' 
    247           - a 'source', which in this case will be "GSCF" 
    248           - a 'category', which indicates where the field can be found, e.g. "subjects", "samplingEvents" 
    249           - a 'name', which is the the name of the field 
    250          */ 
    251         if(inputObject==null || inputObject == []){ 
     235    /** 
     236     * Format the data contained in the input parameter 'collection' for use as so-called fields, that will be used by the user interface to allow the user to select data from GSCF for visualization 
     237     * @param type A string that indicates the type of field, either "domainfields" or "templatefields". 
     238     * @param collectionOfFields A collection of fields, which could also contain only one item 
     239     * @param source Likely to be "GSCF" 
     240     * @param category The domain that a field (a property in this case) belongs to, e.g. "subjects", "samplingEvents" 
     241     * @return A list containing list objects, containing the following: 
     242     *           - a key 'id' with a value formatted by the createFieldId function 
     243     *           - a key 'source' with a value equal to the input parameter 'source' 
     244     *           - a key 'category' with a value equal to the input parameter 'category' 
     245     *           - a key 'name' with a value equal to the name of the field in question, as determined by the source value 
     246     */ 
     247    def formatGSCFFields(type, collectionOfFields, source, category){ 
     248 
     249        if(collectionOfFields==null || collectionOfFields == []){ 
    252250            return [] 
    253251        } 
    254252        def fields = [] 
    255         if(inputObject instanceof Collection){ 
     253        if(collectionOfFields instanceof Collection){ 
    256254            // Apparently this field is actually a list of fields. 
    257255            // We will call ourselves again with the list's elements as input. 
    258256            // These list elements will themselves go through this check again, effectively flattening the original input 
    259             for(int i = 0; i < inputObject.size(); i++){ 
    260                 fields += formatGSCFFields(type, inputObject[i], source, category) 
     257            for(int i = 0; i < collectionOfFields.size(); i++){ 
     258                fields += formatGSCFFields(type, collectionOfFields[i], source, category) 
    261259            } 
    262260            return fields 
     
    264262            // This is a single field. Format it and return the result. 
    265263            if(type=="domainfields"){ 
    266                 fields << [ "id": createFieldId( id: inputObject.name, name: inputObject.name, source: source, type: category ), "source": source, "category": category, "name": inputObject.name ] 
     264                fields << [ "id": createFieldId( id: collectionOfFields.name, name: collectionOfFields.name, source: source, type: category ), "source": source, "category": category, "name": collectionOfFields.name ] 
    267265            } 
    268266            if(type=="templatefields"){ 
    269                 fields << [ "id": createFieldId( id: inputObject.id, name: inputObject.name, source: source, type: category ), "source": source, "category": category, "name": inputObject.name ] 
     267                fields << [ "id": createFieldId( id: collectionOfFields.id, name: collectionOfFields.name, source: source, type: category ), "source": source, "category": category, "name": collectionOfFields.name ] 
    270268            } 
    271269            return fields 
     
    275273        /** 
    276274         * Retrieves data for the visualization itself. 
     275     * Returns, based on the field ids contained in the parameters given by the user, a map containing the actual data and instructions on how the data should be visualized. 
     276     * @return A map containing containing (at least, in the case of a barchart) the following: 
     277     *           - a key 'type' containing the type of chart that will be visualized 
     278     *           - a key 'xaxis' containing the title and unit that should be displayed for the x-axis 
     279     *           - a key 'yaxis' containing the title and unit that should be displayed for the y-axis* 
     280     *           - a key 'series' containing a list, that contains one or more maps, which contain the following: 
     281     *                - a key 'name', containing, for example, a feature name or field name 
     282     *                - a key 'y', containing a list of y-values 
     283     *                - a key 'error', containing a list of, for example, standard deviation or standard error of the mean values, each having the same index as the 'y'-values they are associated with 
    277284         */ 
    278285        def getData = { 
     
    750757        } 
    751758 
     759    /** 
     760     * Set the response code and an error message 
     761     * @param code HTTP status code 
     762     * @param msg Error message, string 
     763     */ 
    752764    protected void returnError(code, msg){ 
    753765        response.sendError(code , msg) 
    754766    } 
    755767 
     768    /** 
     769     * Determines what type of data a field contains 
     770     * @param studyId An id that can be used with Study.get/1 to retrieve a study from the database 
     771     * @param fieldId The field id as returned from the client, will be used to retrieve the data required to determine the type of data a field contains 
     772     * @return Either CATEGORICALDATA of NUMERICALDATA 
     773     */ 
    756774    protected int determineFieldType(studyId, fieldId){ 
    757775        // Parse the fieldId as given by the user 
     
    769787                    TemplateField tf = TemplateField.get(parsedField.id) 
    770788                    if(tf.type=="DOUBLE" || tf.type=="LONG" || tf.type=="DATE" || tf.type=="RELTIME"){ 
    771                         return numericalData 
     789                        return NUMERICALDATA 
    772790                    } else { 
    773                         return categoricalData 
     791                        return CATEGORICALDATA 
    774792                    } 
    775793                } catch(Exception e){ 
    776794                    log.error("VisualizationController: determineFieldType: "+e) 
    777795                    // If we cannot figure out what kind of a datatype a piece of data is, we treat it as categorical data 
    778                     return categoricalData 
     796                    return CATEGORICALDATA 
    779797                } 
    780798            } else { 
     
    784802                        case "Study": 
    785803                        case "studies": 
    786                             return determineCategoryFromClass(Study.fields[parsedField.name].type) 
     804                            return determineCategoryFromClass(Study.class.getDeclaredField(parsedField.name).type) 
    787805                            break 
    788806                        case "Subject": 
    789807                        case "subjects": 
    790                             return determineCategoryFromClass(Subject.fields[parsedField.name].type) 
     808                            return determineCategoryFromClass(Subject.class.getDeclaredField(parsedField.name).type) 
    791809                            break 
    792810                        case "Sample": 
    793811                        case "samples": 
    794                             return determineCategoryFromClass(Sample.fields[parsedField.name].type) 
     812                            return determineCategoryFromClass(Sample.class.getDeclaredField(parsedField.name).type) 
    795813                            break 
    796814                        case "Event": 
    797815                        case "events": 
    798                             return determineCategoryFromClass(Event.fields[parsedField.name].type) 
     816                            return determineCategoryFromClass(Event.class.getDeclaredField(parsedField.name).type) 
    799817                            break 
    800818                        case "SamplingEvent": 
    801819                        case "samplingEvents": 
    802                             return determineCategoryFromClass(SamplingEvent.fields[parsedField.name].type) 
     820                            return determineCategoryFromClass(SamplingEvent.class.getDeclaredField(parsedField.name).type) 
    803821                            break 
    804822                        case "Assay": 
    805823                        case "assays": 
    806                             return determineCategoryFromClass(Assay.fields[parsedField.name].type) 
     824                            return determineCategoryFromClass(Assay.class.getDeclaredField(parsedField.name).type) 
    807825                            break 
    808826                    } 
    809827                } catch(Exception e){ 
    810828                    log.error("VisualizationController: determineFieldType: "+e) 
     829                    e.printStackTrace() 
    811830                    // If we cannot figure out what kind of a datatype a piece of data is, we treat it as categorical data 
    812                     return categoricalData 
     831                    return CATEGORICALDATA 
    813832                } 
    814833            } 
    815  
    816             // Check parsedField.id == number 
    817834                } else { 
    818835            data = getModuleData( study, study.getSamples(), parsedField.source, parsedField.name ); 
     
    823840    } 
    824841 
    825     protected int determineCategoryFromClass(inputObject){ 
    826         if(inputObject==java.lang.String){ 
    827             return categoricalData 
     842    /** 
     843     * Determines a field category, based on the input parameter 'classObject', which is an instance of type 'class' 
     844     * @param classObject 
     845     * @return Either CATEGORICALDATA of NUMERICALDATA 
     846     */ 
     847    protected int determineCategoryFromClass(classObject){ 
     848        println "classObject: "+classObject+", of class: "+classObject.class 
     849        if(classObject==java.lang.String){ 
     850            return CATEGORICALDATA 
    828851        } else { 
    829             return numericalData 
    830         } 
    831     } 
    832  
     852            return NUMERICALDATA 
     853        } 
     854    } 
     855 
     856    /** 
     857     * Determines a field category based on the actual data contained in the field. The parameter 'inputObject' can be a single item with a toString() function, or a collection of such items. 
     858     * @param inputObject Either a single item, or a collection of items 
     859     * @return Either CATEGORICALDATA of NUMERICALDATA 
     860     */ 
    833861    protected int determineCategoryFromData(inputObject){ 
    834862        def results = [] 
     
    841869        } else { 
    842870            if(inputObject.toString().isDouble()){ 
    843                 results << numericalData 
     871                results << NUMERICALDATA 
    844872            } else { 
    845                 results << categoricalData 
     873                results << CATEGORICALDATA 
    846874            } 
    847875        } 
     
    851879        if(results.size()>1){ 
    852880            // If we cannot figure out what kind of a datatype a piece of data is, we treat it as categorical data 
    853             results[0] = categoricalData 
     881            results[0] = CATEGORICALDATA 
    854882        } 
    855883 
     
    857885    } 
    858886 
     887 
     888    /** 
     889     * Properly formats the object that will be returned to the client. Also adds an informational message, if that message has been set by a function. Resets the informational message to the empty String. 
     890     * @param returnData The object containing the data 
     891     * @return results A JSON object 
     892     */ 
    859893    protected void sendResults(returnData){ 
    860894        def results = [:] 
     
    867901    } 
    868902 
     903    /** 
     904     * Properly formats an informational message that will be returned to the client. Resets the informational message to the empty String. 
     905     * @param returnData The object containing the data 
     906     * @return results A JSON object 
     907     */ 
    869908    protected void sendInfoMessage(){ 
    870909        def results = [:] 
     
    874913    } 
    875914 
    876     /* 
    877         Combine several blocks of formatted data into one 
     915    /** 
     916     * Combine several blocks of formatted data into one. These blocks have been formatted by the formatData function. 
     917     * @param inputData Contains a list of maps, of the following format 
     918     *          - a key 'series' containing a list, that contains one or more maps, which contain the following: 
     919     *            - a key 'name', containing, for example, a feature name or field name 
     920     *            - a key 'y', containing a list of y-values 
     921     *            - a key 'error', containing a list of, for example, standard deviation or standard error of the mean values, 
    878922     */ 
    879923    protected def formatCategoryData(inputData){ 
     
    890934        return ret 
    891935    } 
     936 
     937    /** 
     938     * Given two objects of either CATEGORICALDATA or NUMERICALDATA 
     939     * @param rowType The type of the data that has been selected for the row, either CATEGORICALDATA or NUMERICALDATA 
     940     * @param columnType The type of the data that has been selected for the column, either CATEGORICALDATA or NUMERICALDATA 
     941     * @return 
     942     */ 
     943    protected def determineVisualizationTypes(rowType, columnType){ 
     944         def types = [] 
     945        if(rowType==CATEGORICALDATA){ 
     946            if(columnType==CATEGORICALDATA){ 
     947                types = [ [ "id": "table", "name": "Table"] ]; 
     948            } 
     949            if(columnType==NUMERICALDATA){ 
     950                types = [ [ "id": "horizontal_barchart", "name": "Horizontal barchart"] ]; 
     951            } 
     952        } 
     953        if(rowType==NUMERICALDATA){ 
     954            if(columnType==CATEGORICALDATA){ 
     955                types = [ [ "id": "barchart", "name": "Barchart"], [ "id": "linechart", "name": "Linechart"] ]; 
     956            } 
     957            if(columnType==NUMERICALDATA){ 
     958                types = [ [ "id": "scatterplot", "name": "Scatterplot"], [ "id": "linechart", "name": "Linechart"] ]; 
     959            } 
     960        } 
     961        return types 
     962    } 
    892963}