1 /**
  2  * @fileOverview
  3  * @author <a href="https://www.labkey.org">LabKey</a> (<a href="mailto:info@labkey.com">info@labkey.com</a>)
  4  * @license Copyright (c) 2010-2016 LabKey Corporation
  5  * <p/>
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  * <p/>
 10  * http://www.apache.org/licenses/LICENSE-2.0
 11  * <p/>
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing permissions and
 16  * limitations under the License.
 17  * <p/>
 18  */
 19 
 20 /**
 21  * @namespace Visualization static class to programmatically retrieve visualization-ready data.  Also allows
 22  * persistence of various visualization types.
 23  */
 24 LABKEY.Query.Visualization = new function() {
 25 
 26     function formatParams(config)
 27     {
 28         var params = {};
 29 
 30         if (config.filters && config.filters.length)
 31         {
 32             params['filters'] = config.filters;
 33         }
 34 
 35         if (config.dateMeasures !== undefined)
 36             params.dateMeasures = config.dateMeasures;
 37 
 38         if (config.allColumns !== undefined)
 39             params.allColumns = config.allColumns;
 40 
 41         if (config.showHidden !== undefined)
 42             params.showHidden = config.showHidden;
 43 
 44         return params;
 45     }
 46 
 47     // Create a thin wrapper around the success callback capable of converting the persisted (string/JSON) visualization
 48     // configuration into a native JavaScript object before handing it off to the caller:
 49     function getVisualizatonConfigConverterCallback(successCallback, scope)
 50     {
 51         return function(data, response, options)
 52         {
 53             //ensure data is JSON before trying to decode
 54             var json = null;
 55             if (data && data.getResponseHeader && data.getResponseHeader('Content-Type')
 56                     && data.getResponseHeader('Content-Type').indexOf('application/json') >= 0)
 57             {
 58                 json = LABKEY.Utils.decode(data.responseText);
 59                 if (json.visualizationConfig)
 60                     json.visualizationConfig = LABKEY.Utils.decode(json.visualizationConfig);
 61             }
 62 
 63             if(successCallback)
 64                 successCallback.call(scope || this, json, response, options);
 65         }
 66     }
 67 
 68     /**
 69      * This is used internally to automatically parse returned JSON and call another success function. It is based off of
 70      * LABKEY.Utils.getCallbackWrapper, however, it will call the createMeasureFn function before calling the success function.
 71      * @param createMeasureFn
 72      * @param fn
 73      * @param scope
 74      */
 75     function getSuccessCallbackWrapper(createMeasureFn, fn, scope)
 76     {
 77         return function(response, options)
 78         {
 79             //ensure response is JSON before trying to decode
 80             var json = null;
 81             var measures = null;
 82             if (response && response.getResponseHeader && response.getResponseHeader('Content-Type')
 83                     && response.getResponseHeader('Content-Type').indexOf('application/json') >= 0)
 84             {
 85                 json = LABKEY.Utils.decode(response.responseText);
 86                 measures = createMeasureFn(json);
 87             }
 88 
 89             if(fn)
 90                 fn.call(scope || this, measures, response);
 91         };
 92     }
 93 
 94     /*-- public methods --*/
 95     /** @scope LABKEY.Query.Visualization */
 96     return {
 97         getTypes : function(config) {
 98 
 99             function createTypes(json)
100             {
101                 if (json.types && json.types.length)
102                 {
103                     // for now just return the raw object array
104                     return json.types;
105                 }
106                 return [];
107             }
108 
109 
110             LABKEY.Ajax.request(
111             {
112                 url : LABKEY.ActionURL.buildURL("visualization", "getVisualizationTypes"),
113                 method : 'GET',
114                 success: getSuccessCallbackWrapper(createTypes, LABKEY.Utils.getOnSuccess(config), config.scope),
115                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
116             });
117         },
118 
119         /**
120          * Returns the set of plottable measures found in the current container.
121          * @param config An object which contains the following configuration properties.
122          * @param {Array} config.filters An array of {@link LABKEY.Query.Visualization.Filter} objects.
123          * @param {Boolean} [config.dateMeasures] Indicates whether date measures should be returned instead of numeric measures.
124          * Defaults to false.
125          * @param {Function} config.success Function called when execution succeeds. Will be called with one argument:
126          * <ul><li><b>measures</b>: an array of {@link LABKEY.Query.Visualization.Measure} objects.</li>
127          * @param {Function} [config.failure] Function called when execution fails.  Called with the following parameters:
128          * <ul>
129          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
130          * <li><b>response:</b> The XMLHttpResponse object</li>
131          * </ul>
132          */
133         getMeasures : function(config) {
134 
135             function createMeasures(json)
136             {
137                 var measures = [];
138                 if (json.measures && json.measures.length)
139                 {
140                     for (var i=0; i < json.measures.length; i++)
141                         measures.push(new LABKEY.Query.Visualization.Measure(json.measures[i]));
142                 }
143                 return measures;
144             }
145 
146             var params = formatParams(config);
147 
148             LABKEY.Ajax.request(
149             {
150                 url : config.endpoint || LABKEY.ActionURL.buildURL("visualization", "getMeasures"),
151                 method : 'GET',
152                 success: getSuccessCallbackWrapper(createMeasures, LABKEY.Utils.getOnSuccess(config), config.scope),
153                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
154                 params : params
155             });
156         },
157 
158         /**
159          * Returns a resultset suitable for visualization based on requested measures and dimensions.
160          * @param config An object which contains the following configuration properties.
161          * @param {Array} config.measures An array of objects with the following structure:
162          * <ul>
163          *      <li><b>measure</b>: Generally an augmented {@link LABKEY.Query.Visualization.Measure}, but can be any object
164          *          with the following properties:
165          *          <ul>
166          *              <li><b>schemaName</b>: The name of the schema containing the query that contains this measure.</li>
167          *              <li><b>queryName</b>: The name of the query containing this measure.</li>
168          *              <li><b>name</b>: The  name of the column containing this measure.</li>
169          *              <li><b>type</b>: The data type of this measure.</li>
170          *              <li><b>isDemographic</b>: Boolean (default false). Indicates whether the measure is Demographic data.</li>
171          *              <li><b>alias</b>: String. </li>
172          *              <li><b>values</b>: Optional.  If provided, results will be filtered to include only the provided values.</li>
173          *              <li><b>allowNullResults</b>: Optional, defaults to true.  If true, this measure will be joined to other measures via an outer join, which will allow results
174          *                  from other measures at timepoints not present for this measure (possibly resulting in null/blank values for this measure).  If false, other measures will be inner joined
175          *                  to this measure, which will produce a dataset without null values for this measure, but which may not include all data from joined measures.</li>
176          *              <li><b>aggregate</b>: See {@link LABKEY.Query.Visualization.Aggregate}.  Required if a 'dimension' property is specified, ignored otherwise.  Indicates
177          *                                    what data should be returned if pivoting by dimension results in multiple underlying values
178          *                                    per series data point.</li>
179          *          </ul>
180          *      </li>
181          *      <li><b>dateOptions</b>: Optional if this measure's axis.timeAxis property is true, ignored otherwise.  Has the following child properties.
182          *                      Either zeroDateCol or ZeroDayVisitTag may be specified, but not both.
183          *          <ul>
184          *              <li><b>dateCol</b>: A measure object (with properties for name, queryName, and
185          *                                              schemaName) of type date specifying the measure date.</li>
186          *              <li><b>zeroDateCol</b>: A measure object (with properties for name, queryName, and
187          *                                              schemaName) of type date specifiying the zero date, used to align data points in terms of days, weeks, or months.</li>
188          *              <li><b>zeroDayVisitTag</b>: String. A VisitTag that will be used to find the ParticipantVisit used to align data points.  </li>
189          *              <li><b>interval</b>: See {@link LABKEY.Query.Visualization.Interval}.  The type of interval that should be
190          *                                              calculated between the measure date and the zero date (if zeroDateCol is specified)
191          *                                              or zero day (if zeroDayVisitTag is specified).</li>
192          *              <li><b>useProtocolDay</b>: Boolean (default true). If true, zeroDayVisitTag uses ParticipantVisit.ProtocolDay to calculate offsets;
193          *                                              if false ParticipantVisit.Day is used.</li>
194          *          </ul>
195          *      </li>
196          *      <li><b>time</b>:
197          *          String: "date" indicates this measure is date-based. "time" indicates it is time-based.
198          *      </li>
199          *      <li><b>dimension</b>:  Used to pivot a resultset into multiple series.  Generally an augmented
200          *          {@link LABKEY.Query.Visualization.Dimension}, but can be any object with the following properties:
201          *          <ul>
202          *              <li><b>name</b>: The name of this dimension.</li>
203          *              <li><b>schemaName</b>: The name of the schema containing the query that contains this dimension.</li>
204          *              <li><b>queryName</b>: The name of the query containing this dimension.</li>
205          *              <li><b>type</b>: The data type of this dimension.</li>
206          *              <li><b>values</b>: Optional.  If provided, results will be filtered to include only the named series.</li>
207          *          </ul>
208          *      </li>
209          * </ul>
210          * @param {Array} [config.sorts] Generally an array of augmented {@link LABKEY.Query.Visualization.Dimension} or {@link LABKEY.Query.Visualization.Measure}
211          * objects, but can be an array of any objects with the following properties:
212          * <ul>
213          *  <li><b>name</b>: The name of this dimension.</li>
214          *  <li><b>schemaName</b>: The name of the schema containing the query that contains this dimension.</li>
215          *  <li><b>queryName</b>: The name of the query containing this dimension.</li>
216          *  <li><b>values</b>: Optional.  If provided, results will be filtered to include only the specified values.</li>
217          * </ul>
218          * @param {Boolean} [config.metaDataOnly] Default false. If true, response will no include the actual data rows, just metadata.
219          * @param {Boolean} [config.joinToFirst] Default false. If true, all measures will be joined to the first measure in the array instead of to the previous measure.
220 
221          * @param {Function} config.success Function called when execution succeeds. Will be called with three arguments:
222          * <ul>
223          *  <li><b>data</b>: the parsed response data ({@link LABKEY.Query.SelectRowsResults})</li>
224          *  <li><b>request</b>: the XMLHttpRequest object</li>
225          *  <li><b>options</b>: a request options object ({@link LABKEY.Query.SelectRowsOptions})</li>
226          * </ul>
227          * @param {Function} [config.failure] Function called when execution fails.  Called with the following parameters:
228          * <ul>
229          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
230          * <li><b>response:</b> The XMLHttpResponse object</li>
231          * </ul>
232          */
233         getData : function(config) {
234 
235             var params = {
236                 measures : [],
237                 sorts : config.sorts,
238 
239                 // @deprecated -- created for issue 11627
240                 // The filterUrl and filterQuery are used in tandem to apply a filter from a URL. Use the filterArray
241                 // option on each measure to apply filters.
242                 filterQuery: config.filterQuery, // e.g. 'study.Lab Results'
243                 filterUrl: config.filterUrl, // e.g. '/labkey/study/StudyFolder/dataset.view?Dataset.Column%7Egt=value'
244 
245                 limit   : config.limit,
246                 groupBys: config.groupBys,
247                 metaDataOnly: config.metaDataOnly,
248 
249                 // specify that all source queries should join back to the first measure
250                 joinToFirst: config.joinToFirst === true
251             };
252 
253             // clone measures
254             var measure, filters, m, f, asURL, fa;
255             for (m=0; m < config.measures.length; m++)
256             {
257                 var c = config.measures[m];
258 
259                 measure = {
260                     measure: c.measure,
261                     time: c.time
262                 };
263 
264                 if (c.dimension)
265                 {
266                     measure.dimension = c.dimension;
267                 }
268 
269                 if (c.dateOptions)
270                 {
271                     measure.dateOptions = c.dateOptions;
272                 }
273 
274                 if (c.filterArray)
275                 {
276                     measure.filterArray = c.filterArray;
277 
278                     // assume it is an array of LABKEY.Filter instances, convert each filter to it's URL parameter equivalent
279                     filters = [];
280                     for (f=0; f < measure.filterArray.length; f++)
281                     {
282                         fa = measure.filterArray[f];
283                         if (fa && fa.getURLParameterName)
284                         {
285                             asURL = encodeURIComponent(fa.getURLParameterName()) + "=" + encodeURIComponent(fa.getURLParameterValue());
286                             filters.push(asURL);
287                         }
288                     }
289                     measure['filterArray'] = filters;
290                 }
291 
292                 params.measures.push(measure);
293             }
294 
295             // issue 21418: support for parameterized queries
296             var urlParams = {};
297             if (config.parameters)
298             {
299                 for (var propName in config.parameters)
300                 {
301                     if (config.parameters.hasOwnProperty(propName))
302                         urlParams['visualization.param.' + propName] = config.parameters[propName];
303                 }
304             }
305 
306             var endpoint = config.endpoint || LABKEY.ActionURL.buildURL("visualization", "getData", config.containerPath);
307             endpoint += '?' + LABKEY.ActionURL.queryString(urlParams);
308 
309             LABKEY.Ajax.request(
310             {
311                 url : endpoint,
312                 method : 'POST',
313                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope, false),
314                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
315                 jsonData : params,
316                 headers : {
317                     'Content-Type' : 'application/json'
318                 }
319             });
320         },
321 
322         /**
323          * Saves a visualization for future use.  Saved visualizations appear in the study 'views' webpart.  If the
324          * visualization is scoped to a specific query, it will also appear in the views menu for that query.
325          * @param config An object which contains the following configuration properties.
326          * @param {String} config.name The name this visualization should be saved under.
327          * @param {String} config.type The type of visualization being saved.  Should be an instance of {@link LABKEY.Query.Visualization.Type}.
328          * @param {Object} config.visualizationConfig An arbitrarily complex JavaScript object that contains all information required to
329          * recreate the report.
330          * @param {Boolean} [config.replace] Whether this 'save' call should replace an existing report with the same name.
331          * If false, the call to 'save' will fail if another report with the same name exists.
332          * @param {String} [config.description] A description of the saved report.
333          * @param {String} [config.shared] Boolean indicating whether this report is viewable by all users with read
334          * permissions to the visualization's folder.  If false, only the creating user can see the visualization.  Defaults to true.
335          * @param {Boolean} [config.thumbnailType] String indicating whether a thumbnail should be auto-generated ('AUTO'), no thumbnail
336          * should be saved ('NONE'), or the existing custom thumbnail should be kept ('CUSTOM')
337          * @param {String} [config.svg] String svg to be used to generate a thumbnail
338          * @param {String} [config.schemaName] Optional, but required if config.queryName is provided.  Allows the visualization to
339          * be scoped to a particular query.  If scoped, this visualization will appear in the 'views' menu for that query.
340          * @param {String} [config.queryName] Optional, but required if config.schemaName is provided.  Allows the visualization to
341          * be scoped to a particular query.  If scoped, this visualization will appear in the 'views' menu for that query.
342          * @param {Function} config.success Function called when execution succeeds. Will be called with one arguments:
343          * <ul>
344          *  <li><b>result</b>: an object with two properties:
345          *      <ul>
346          *          <li><b>name</b>: the name of the saved visualization</li>
347          *          <li><b>visualizationId</b>: a unique integer identifier for this saved visualization</li>
348          *      </ul>
349          *  <li><b>request</b>: the XMLHttpRequest object</li>
350          *  <li><b>options</b>: a request options object</li>
351          *  </ul>
352          * @param {Function} [config.failure] Function called when execution fails.  Called with the following parameters:
353          * <ul>
354          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
355          * <li><b>response:</b> The XMLHttpResponse object</li>
356          * </ul>
357          */
358         save : function(config)
359         {
360             var params = {
361                 name : config.name,
362                 description : config.description,
363                 json : LABKEY.Utils.encode(config.visualizationConfig),
364                 replace: config.replace,
365                 shared: config.shared,
366                 thumbnailType: config.thumbnailType,
367                 svg : config.svg,
368                 type : config.type,
369                 schemaName: config.schemaName,
370                 queryName: config.queryName
371             };
372 
373             LABKEY.Ajax.request(
374             {
375                 url : LABKEY.ActionURL.buildURL("visualization", "saveVisualization"),
376                 method : 'POST',
377                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope, false),
378                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
379                 jsonData : params,
380                 headers : {
381                     'Content-Type' : 'application/json'
382                 }
383             });
384         },
385 
386         /**
387          * Retrieves a saved visualization.  See {@link LABKEY.Query.Visualization.save}.
388          * @param config An object which contains the following configuration properties.
389          * @param {String} config.name The name this visualization to be retrieved.
390          * @param {String} [config.schemaName] Optional, but required if config.queryName is provided.  Limits the search for
391          * the visualization to a specific schema and query.  Note that visualization names are unique within a container
392          * (regardless of schema and query), so these additional optional parameters are only useful in a small number of circumstances.
393          * @param {String} [config.queryName] Optional, but required if config.schemaName is provided.  Limits the search for
394          * the visualization to a specific schema and query.  Note that visualization names are unique within a container
395          * (regardless of schema and query), so these additional optional parameters are only useful in a small number of circumstances.
396          * @param {Function} config.success Function called when execution succeeds. Will be called with one arguments:
397          * <ul>
398          *  <li><b>result</b>: an object with two properties:
399          *      <ul>
400          *          <li><b>name</b>: The name of the saved visualization</li>
401          *          <li><b>description</b>: The description of the saved visualization</li>
402          *          <li><b>type</b>: The visualization type</li>
403          *          <li><b>schemaName</b>: The schema to which this visualization has been scoped, if any</li>
404          *          <li><b>queryName</b>: The query to which this visualization has been scoped, if any</li>
405          *          <li><b>visualizationConfig</b>: The configuration object provided to {@link LABKEY.Query.Visualization.save}</li>
406          *      </ul>
407          *  </li>
408          *  <li><b>request</b>: the XMLHttpRequest object</li>
409          *  <li><b>options</b>: a request options object</li>
410          * </ul>
411          * @param {Function} [config.failure] Function called when execution fails.  Called with the following parameters:
412          * <ul>
413          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
414          * <li><b>response:</b> The XMLHttpResponse object</li>
415          * </ul>
416          */
417         get : function(config)
418         {
419             var params = {
420                 reportId: config.reportId,
421                 name : config.name,
422                 schemaName: config.schemaName,
423                 queryName: config.queryName
424             };
425 
426             // Get the standard callback function (last in the success callback chain):
427             var successCallback = LABKEY.Utils.getOnSuccess(config);
428 
429             // wrap the callback to convert the visualizationConfig property from a JSON string into a native javascript object:
430             successCallback = getVisualizatonConfigConverterCallback(successCallback, config.scope);
431 
432             LABKEY.Ajax.request(
433             {
434                 url : LABKEY.ActionURL.buildURL("visualization", "getVisualization"),
435                 method : 'POST',
436                 initialConfig: config,
437                 success: successCallback,
438                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
439                 jsonData : params,
440                 headers : {
441                     'Content-Type' : 'application/json'
442                 }
443             });
444         },
445 
446         /**
447          * Retrieves a saved visualization based on identifying parameters found on the current URL.  Method returns true or false,
448          * depending on whether the URL contains a saved visualization identifier.  If true, the success or failure callback
449          * function will be called just as with {@link LABKEY.Query.Visualization.get}.  If false, no callbacks will be called.
450          * This method allows callers to use a single method to retrieve saved visualizations, regardless of how they are identified on the URL.
451          * @param config An object which contains the following configuration properties.
452          * @param {Function} config.success Function called when the saved visualization was successfully retrieved. See {@link LABKEY.Query.Visualization.get} for details.
453          * @param {Function} [config.failure] Function called when the saved visualization could not be retrieved.  See {@link LABKEY.Query.Visualization.get} for details.
454          * @return Boolean indicating whether the current URL includes parameters that identify a saved visualization.
455          */
456         getFromUrl : function(config)
457         {
458             var params = config || {};
459 
460             params.success = LABKEY.Utils.getOnSuccess(config);
461             params.failure = LABKEY.Utils.getOnFailure(config);
462 
463             var urlParams = LABKEY.ActionURL.getParameters();
464             var valid = false;
465             if (params.reportId)
466             {
467                 valid = true;
468             }
469             else
470             {
471                 if (urlParams.name)
472                 {
473                     params.name = urlParams.name;
474                     params.schemaName = urlParams.schemaName;
475                     params.queryName = urlParams.queryName;
476                     valid = true;
477                 }
478             }
479 
480             if (valid)
481                 LABKEY.Query.Visualization.get(params);
482             return valid;
483         },
484 
485         getDataFilterFromURL : function()
486         {
487             var urlParams = LABKEY.ActionURL.getParameters();
488             return urlParams['filterUrl'];
489         }
490     };
491 };
492 
493 /**
494  * @deprecated Use {@link LABKEY.Query.Visualization}
495  */
496 LABKEY.Visualization = LABKEY.Query.Visualization;
497 
498 /**
499  * @namespace Visualization Measures are plottable data elements (columns).  They may be of numeric or date types.
500  */
501 LABKEY.Query.Visualization.Measure = function(config) {
502 
503     LABKEY.Utils.apply(this, config);
504 };
505 
506 /**
507  * Returns the name of the query associated with this dimension.
508  */
509 LABKEY.Query.Visualization.Measure.prototype.getQueryName = function() {
510     return this.queryName;
511 };
512 /**
513  * Returns the name of the schema assocated with this dimension.
514  */
515 LABKEY.Query.Visualization.Measure.prototype.getSchemaName = function() {
516     return this.schemaName;
517 };
518 /**
519  * Returns whether this dimension is part of a user-defined query (versus a built-in/system-provided query).
520  */
521 LABKEY.Query.Visualization.Measure.prototype.isUserDefined = function() {
522     return this.isUserDefined;
523 };
524 /**
525  * Returns the column name of this dimension.
526  */
527 LABKEY.Query.Visualization.Measure.prototype.getName = function() {
528     return this.name;
529 };
530 /**
531  * Returns the label of this dimension.
532  */
533 LABKEY.Query.Visualization.Measure.prototype.getLabel = function() {
534     return this.label;
535 };
536 /**
537  * Returns the data types of this dimension.
538  */
539 LABKEY.Query.Visualization.Measure.prototype.getType = function() {
540     return this.type;
541 };
542 /**
543  * Returns a description of this dimension.
544  */
545 LABKEY.Query.Visualization.Measure.prototype.getDescription = function() {
546     return this.description;
547 };
548 /**
549  * Returns the set of available {@link LABKEY.Query.Visualization.Dimension} objects for this measure.
550  * @param config An object which contains the following configuration properties.
551  * @param config.includeDemographics {Boolean} Applies only to measures from study datsets.
552  * Indicates whether dimensions from demographic datasets should be included
553  * in the returned set.  If false, only dimensions from the measure's query will be returned.
554  * @param {Function} config.success Function called when execution succeeds. Will be called with one argument:
555  <ul>
556  <li><b>values</b>: an array of unique dimension values</li>
557  </ul>
558  * @param {Function} [config.failure] Function called when execution fails.  Called with the following parameters:
559  * <ul>
560  * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
561  * <li><b>response:</b> The XMLHttpResponse object</li>
562  * </ul>
563  */
564 LABKEY.Query.Visualization.Measure.prototype.getDimensions = function(config) {
565 
566     var params = {queryName: this.queryName, schemaName: this.schemaName};
567 
568     if (config.includeDemographics)
569         params['includeDemographics'] = config.includeDemographics;
570 
571     function createDimensions(json)
572     {
573         var dimensions = [];
574         if (json.dimensions && json.dimensions.length)
575         {
576             for (var i=0; i < json.dimensions.length; i++)
577                 dimensions.push(new LABKEY.Query.Visualization.Dimension(json.dimensions[i]));
578         }
579         return dimensions;
580     }
581 
582     LABKEY.Ajax.request(
583             {
584                 url : LABKEY.ActionURL.buildURL("visualization", "getDimensions"),
585                 method : 'GET',
586                 params : params,
587                 success: getSuccessCallbackWrapper(createDimensions, LABKEY.Utils.getOnSuccess(config), config.scope),
588                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
589             });
590 };
591 
592 /**
593  * @deprecated Use {@link LABKEY.Query.Visualization.Measure}
594  */
595 LABKEY.Visualization.Measure = LABKEY.Query.Visualization.Measure;
596 
597 /**
598  * @namespace Visualization Dimensions are data elements (columns) on which {@link LABKEY.Query.Visualization.Measure} objects
599  *  can be pivoted or transformed.  For example, the 'Analyte Name' dimension may be used to pivit a single 'Result' measure
600  * into one series per Analyte.
601  */
602 LABKEY.Query.Visualization.Dimension = function(config) {
603     LABKEY.Utils.apply(this, config);
604 };
605 /**
606  * Returns the name of the query associated with this dimension.
607  */
608 LABKEY.Query.Visualization.Dimension.getQueryName = function() {
609     return this.queryName;
610 };
611 /**
612  * Returns the name of the schema assocated with this dimension.
613  */
614 LABKEY.Query.Visualization.Dimension.getSchemaName = function() {
615     return this.schemaName;
616 };
617 
618 /**
619  * Returns whether this dimension is part of a user-defined query (versus a built-in/system-provided query).
620  */
621 LABKEY.Query.Visualization.Dimension.isUserDefined = function() {
622     return this.isUserDefined;
623 };
624 
625 /**
626  * Returns the column name of this dimension.
627  */
628 LABKEY.Query.Visualization.Dimension.getName = function() {
629     return this.name;
630 };
631 
632 /**
633  * Returns the label of this dimension.
634  */
635 LABKEY.Query.Visualization.Dimension.getLabel = function() {
636     return this.label;
637 };
638 
639 /**
640  * Returns the data types of this dimension.
641  */
642 LABKEY.Query.Visualization.Dimension.getType = function() {
643     return this.type;
644 };
645 
646 /**
647  * Returns a description of this dimension.
648  */
649 LABKEY.Query.Visualization.Dimension.getDescription = function() {
650     return this.description;
651 };
652 
653 /**
654  * Returns the set of available unique values for this dimension.
655  * @param config An object which contains the following configuration properties.
656  * @param {Function} config.success Function called when execution succeeds. Will be called with one argument:
657  <ul>
658  <li><b>values</b>: an array of unique dimension values</li>
659  </ul>
660  * @param {Function} [config.failure] Function called when execution fails.  Called with the following parameters:
661  * <ul>
662  * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
663  * <li><b>response:</b> The XMLHttpResponse object</li>
664  * </ul>
665  */
666 LABKEY.Query.Visualization.Dimension.getValues = function(config) {
667 
668     var params = {queryName: this.queryName, schemaName: this.schemaName, name: this.name};
669     function createValues(json)
670     {
671         if (json.success && json.values)
672             return json.values;
673         return [];
674     }
675 
676     LABKEY.Ajax.request(
677             {
678                 url : LABKEY.ActionURL.buildURL("visualization", "getDimensionValues"),
679                 method : 'GET',
680                 params : params,
681                 success: getSuccessCallbackWrapper(createValues, LABKEY.Utils.getOnSuccess(config), config.scope),
682                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
683             });
684 };
685 
686 /**
687  * @deprecated Use {@link LABKEY.Query.Visualization.Dimension}
688  */
689 LABKEY.Visualization.Dimension = LABKEY.Query.Visualization.Dimension;
690 
691 /**
692  * @namespace Visualization Helper class to allow filtering of the measures returned by the
693  * {@link LABKEY.Query.Visualization.getMeasures} method.
694  */
695 LABKEY.Query.Visualization.Filter = new function()
696 {
697     function getURLParameterValue(config)
698     {
699         var params = [config.schemaName];
700 
701         if (config.queryName)
702             params.push(config.queryName);
703         else
704             params.push('~');
705         
706         if (config.queryType)
707             params.push(config.queryType);
708 
709         return params.join('|');
710     }
711 
712     /** @scope LABKEY.Query.Visualization.Filter */
713     return {
714         /**
715          * @namespace Visualization Possible query types for measure filters.  See {@link LABKEY.Query.Visualization.Filter}.
716         */
717         QueryType : {
718             /** Return only queries that are built-in to the server */
719             BUILT_IN : 'builtIn',
720             /** Return only queries that are custom (user defined) */
721             CUSTOM : 'custom',
722             /** Return only datasets */
723             DATASETS : 'datasets',
724             /** Return all queries (both built-in and custom) */
725             ALL : 'all'
726         },
727 
728         /**
729          * Creates a new filter object for use in {@link LABKEY.Query.Visualization.getMeasures}.
730          * @param config An object which contains the following configuration properties.
731          * @param {String} config.schemaName Required.  Only measures from the specified schema will be returned.
732          * @param {String} [config.queryName] If specified, only measures from the specified query will be returned.
733          * @param {Object} [config.queryType] If specified, only measures from the specified query types will be returned
734          * Valid values for queryType are:  {@link LABKEY.Query.Visualization.Filter.QueryType}.ALL, {@link LABKEY.Query.Visualization.Filter.QueryType}.BUILT_IN,
735          * and {@link LABKEY.Query.Visualization.Filter.QueryType}.CUSTOM.  By default, all queries will be returned.
736          */
737         create : function(config)
738         {
739             if (!config.schemaName)
740                 throw new Error("Coding Error!", "You must supply a value for schemaName in your configuration object!");
741             else
742                 return getURLParameterValue(config);
743         }
744     };
745 };
746 
747 /**
748  * @deprecated Use {@link LABKEY.Query.Visualization.Filter}
749  */
750 LABKEY.Visualization.Filter = LABKEY.Query.Visualization.Filter;
751 
752 /**
753  * @namespace Visualization Possible aggregates when pivoting a resultset by a dimension.  See  {@link LABKEY.Query.Visualization.getData}.
754  */
755 LABKEY.Query.Visualization.Aggregate = {
756     /** Calculates a sum/total. */
757     SUM: "SUM",
758     /** Calculates an average. */
759     AVG: "AVG",
760     /** Returns the total number of data points. */
761     COUNT: "COUNT",
762     /** Returns the minimum value. */
763     MIN: "MIN",
764     /** Returns the maximum value. */
765     MAX: "MAX"
766 };
767 
768 /**
769  * @deprecated Use {@link LABKEY.Query.Visualization.Aggregate}
770  */
771 LABKEY.Visualization.Aggregate = LABKEY.Query.Visualization.Aggregate;
772 
773 /**
774  * @namespace Visualization Possible intervals for aligning series in time plots.  See  {@link LABKEY.Query.Visualization.getData}.
775  */
776 LABKEY.Query.Visualization.Interval = {
777     /** Align by the number of days since the zero date. */
778     DAY : "DAY",
779     /** Align by the number of weeks since the zero date. */
780     WEEK : "WEEK",
781     /** Align by the number of months since the zero date. */
782     MONTH : "MONTH",
783     /** Align by the number of years since the zero date. */
784     YEAR: "YEAR"
785 };
786 
787 /**
788  * @deprecated Use {@link LABKEY.Query.Visualization.Interval}
789  */
790 LABKEY.Visualization.Interval = LABKEY.Query.Visualization.Interval;
791 
792 /**
793  * @namespace Visualization A predefined set of visualization types, for use in the config.type property in the
794  * {@link LABKEY.Query.Visualization.save} method.
795  */
796 LABKEY.Query.Visualization.Type = {
797     /**
798      * Plots data over time, aligning different series based on configurable start dates.
799      */
800     TimeChart : 'ReportService.TimeChartReport'
801 };
802 
803 /**
804  * @deprecated Use {@link LABKEY.Query.Visualization.Type}
805  */
806 LABKEY.Visualization.Type = LABKEY.Query.Visualization.Type;
807