Changeset 2009


Ignore:
Timestamp:
Sep 8, 2011, 3:18:25 PM (5 years ago)
Author:
taco@…
Message:

Update for the visualization controller, primarily more commenting

File:
1 edited

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}
Note: See TracChangeset for help on using the changeset viewer.