1 /*
  2  * Copyright (c) 2012-2017 LabKey Corporation
  3  *
  4  * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
  5  */
  6 
  7 if(!LABKEY.vis.Geom){
  8     /**
  9      * @namespace The Geom namespace. Used for all geoms, such as {@link LABKEY.vis.Geom.Point},
 10      * {@link LABKEY.vis.Geom.Path}, etc.
 11      */
 12 	LABKEY.vis.Geom = {};
 13 }
 14 
 15 /**
 16  * @class The base XY geom. Extended by all geoms that will be plotted on a grid using a Cartesian [X,Y] coordinate system.
 17  *      This is a base class meant to be used internally only.
 18  */
 19 LABKEY.vis.Geom.XY = function(){
 20     this.type = "XY";
 21     this.xAes = null;
 22     this.yAes = null;
 23     this.typeSubtypeAes = null;
 24     this.parentNameAes = null;
 25     this.childNameAes = null;
 26     this.xScale = null;
 27     this.yScale = null;
 28     this.colorAes = null;
 29     this.colorScale = null;
 30     return this;
 31 };
 32 LABKEY.vis.Geom.XY.prototype.initAesthetics = function(scales, layerAes, parentAes, layerName, index){
 33     this.layerName = layerName;
 34     this.index = index;
 35 
 36     if(layerAes.x){
 37         this.xAes = layerAes.x;
 38         this.xScale = scales.x;
 39     } else if(layerAes.xTop){
 40         this.xAes = layerAes.xTop;
 41         this.xScale = scales.xTop;
 42     }  else if(parentAes.x){
 43         this.xAes = parentAes.x;
 44         this.xScale = scales.x;
 45     } else if(parentAes.xTop){
 46         this.xAes = parentAes.xTop;
 47         this.xScale = scales.xTop;
 48     }
 49 
 50     if (parentAes.xSub && scales.xSub) {
 51         this.xSubAes = parentAes.xSub;
 52         this.xSubScale = scales.xSub;
 53     }
 54 
 55     if(!this.xAes){
 56         console.error('x aesthetic is required for ' + this.type + ' geom to render.');
 57         return false;
 58     }
 59 
 60     if(layerAes.yLeft){
 61         this.yAes = layerAes.yLeft;
 62         this.yScale = scales.yLeft;
 63     } else if(layerAes.yRight){
 64         this.yAes = layerAes.yRight;
 65         this.yScale = scales.yRight;
 66     } else if(parentAes.yLeft){
 67         this.yAes = parentAes.yLeft;
 68         this.yScale = scales.yLeft;
 69     } else if(parentAes.yRight){
 70         this.yAes = parentAes.yRight;
 71         this.yScale = scales.yRight;
 72     }
 73 
 74     this.colorAes = layerAes.color ? layerAes.color : parentAes.color;
 75     this.colorScale = scales.color ? scales.color : null;
 76 
 77     if(!this.yAes){
 78         console.error('y aesthetic is required for ' + this.type + ' geom to render.');
 79         return false;
 80     }
 81 
 82     if (parentAes.typeSubtype) {
 83         this.typeSubtypeAes = parentAes.typeSubtype;
 84     }
 85 
 86     if (parentAes.parentName) {
 87         this.parentNameAes = parentAes.parentName;
 88     }
 89 
 90     if (parentAes.childName) {
 91         this.childNameAes = parentAes.childName;
 92     }
 93 
 94     return true;
 95 };
 96 
 97 LABKEY.vis.Geom.XY.prototype.getVal = function(scale, map, row, isY){
 98     // Takes a row, returns the scaled y value.
 99     var value = map.getValue(row);
100 
101     if(!LABKEY.vis.isValid(value)){
102         if(this.plotNullPoints){
103             return isY ? scale(scale.domain()[0]) + 5 : scale(scale.domain()[0]) - 5;
104         } else {
105             return null;
106         }
107     } else {
108         return scale(value);
109     }
110 };
111 
112 LABKEY.vis.Geom.XY.prototype.getXSub = function(row) {
113     //Takes a row, returns the scaled x subcategory value.
114     return this.getVal(this.xSubScale.scale, this.xSubAes, row, false);
115 };
116 
117 LABKEY.vis.Geom.XY.prototype.getX = function(row){
118     // Takes a row, returns the scaled x value.
119     return this.getVal(this.xScale.scale, this.xAes, row, false);
120 };
121 
122 LABKEY.vis.Geom.XY.prototype.getY = function(row){
123     // Takes a row, returns the scaled y value.
124     return this.getVal(this.yScale.scale, this.yAes, row, true);
125 };
126 
127 LABKEY.vis.Geom.XY.prototype.getTypeSubtype = function(row){
128     // Takes a row, returns the scaled y value.
129     return this.getVal(this.yScale.scale, this.typeSubtypeAes, row, true);
130 };
131 
132 LABKEY.vis.Geom.XY.prototype.getParentY = function(row){
133     // Takes a row, returns the scaled parent name value.
134     return this.getVal(this.yScale.scale, this.parentNameAes, row, true);
135 };
136 
137 /**
138  * @class The Point geom, used to generate points of data on a plot such as points on a line or points in a scatter plot.
139  *      This geom supports the use of size, color, shape, hoverText, and pointClickFn aesthetics from the
140  *      {@link LABKEY.vis.Layer} and/or {@link LABKEY.vis.Plot} objects.
141  * @param {Object} config An object with the following properties:
142  * @param {String} [config.color] (Optional) String used to determine the color of all points. Defaults to black (#000000).
143  * @param {Number} [config.size] (Optional) Number used to determine the size of all points.  Defaults to 5.
144  * @param {Number} [config.opacity] (Optional) Number between 0 and 1, used to determine the opacity of all points. Useful if
145  *      there are overlapping points in the data. Defaults to 1.
146  * @param {Boolean} [config.plotNullPoints] (Optional) Used to toggle whether or not a row of data with the value of null will be
147  *      plotted. If true null or undefined values will be plotted just outside the axis with data. For example if a
148  *      row of data looks like {x: 50, y: null} the point would appear at 50 on the x-axis, and just below the x axis.
149  *      If both x and y values are null the point will be drawn to the bottom left of the origin. Defaults to false.
150  * @param {String} [config.position] (Optional) String with possible value "jitter". If config.position is "jitter" and the
151  *      x or y scale is discrete it will be moved just before or after the position on the grid by a random amount.
152  *      Useful if there is overlapping data. Defaults to undefined.
153  */
154 LABKEY.vis.Geom.Point = function(config){
155     this.type = "Point";
156 
157     if(!config){
158         config = {};
159     }
160     this.color = ('color' in config && config.color != null && config.color != undefined) ? config.color : '#000000';
161     this.size = ('size' in config && config.size != null && config.size != undefined) ? config.size : 5;
162     this.opacity = ('opacity' in config && config.opacity != null && config.opacity != undefined) ? config.opacity : 1;
163     this.plotNullPoints = ('plotNullPoints' in config && config.plotNullPoints != null && config.plotNullPoints != undefined) ? config.plotNullPoints : false;
164     this.position = ('position' in config && config.position != null && config.position != undefined) ? config.position : null;
165 
166     return this;
167 };
168 LABKEY.vis.Geom.Point.prototype = new LABKEY.vis.Geom.XY();
169 LABKEY.vis.Geom.Point.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
170     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
171         return false;
172     }
173 
174     this.shapeAes = layerAes.shape ? layerAes.shape : parentAes.shape;
175     this.shapeScale = scales.shape;
176     this.sizeAes = layerAes.size ? layerAes.size : parentAes.size;
177     this.sizeScale = scales.size;
178     this.hoverTextAes = layerAes.hoverText ? layerAes.hoverText : parentAes.hoverText;
179     this.pointClickFnAes = layerAes.pointClickFn ? layerAes.pointClickFn : parentAes.pointClickFn;
180     this.mouseOverFnAes = layerAes.mouseOverFn ? layerAes.mouseOverFn : parentAes.mouseOverFn;
181     this.mouseOutFnAes = layerAes.mouseOutFn ? layerAes.mouseOutFn : parentAes.mouseOutFn;
182     this.mouseUpFnAes = layerAes.mouseUpFn ? layerAes.mouseUpFn : parentAes.mouseUpFn;
183     this.pointIdAttrAes = layerAes.pointIdAttr ? layerAes.pointIdAttr : parentAes.pointIdAttr;
184 
185     renderer.renderPointGeom(data, this);
186     return true;
187 };
188 
189 /**
190  * @private
191  * @class The Bin geom, used to bin a set of data and generate a plot of binned points. Currently, under development and not for external use. Normally, this is used for scatter x-y based data.
192  * @param config
193  * @returns {LABKEY.vis.Geom.Bin}
194  * @constructor
195  */
196 LABKEY.vis.Geom.Bin = function(config) {
197     this.type = "Bin";
198 
199     if (!config) {
200         config = {};
201     }
202 
203     this.shape = ('shape' in config && config.shape != null && config.shape != undefined) ? config.shape : 'hex';
204     this.colorDomain = ('colorDomain' in config && config.colorDomain != null && config.colorDomain != undefined) ? config.colorDomain : undefined;
205     this.colorRange = ('colorRange' in config && config.colorRange != null && config.colorRange != undefined) ? config.colorRange : ["#e6e6e6", "#085D90"]; // lightish-gray -> labkey blue
206     this.size = ('size' in config && config.size != null && config.size != undefined) ? config.size : 5;
207     this.plotNullPoints = ('plotNullPoints' in config && config.plotNullPoints != null && config.plotNullPoints != undefined) ? config.plotNullPoints : false;
208 
209     return this;
210 };
211 LABKEY.vis.Geom.Bin.prototype = new LABKEY.vis.Geom.XY();
212 LABKEY.vis.Geom.Bin.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
213     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
214         return false;
215     }
216 
217     this.mouseOverFnAes = layerAes.mouseOverFn ? layerAes.mouseOverFn : parentAes.mouseOverFn;
218     this.mouseOutFnAes = layerAes.mouseOutFn ? layerAes.mouseOutFn : parentAes.mouseOutFn;
219     this.mouseUpFnAes = layerAes.mouseUpFn ? layerAes.mouseUpFn : parentAes.mouseUpFn;
220 
221     renderer.renderBinGeom(data, this);
222     return true;
223 };
224 
225 /**
226  * @class The path geom, used to draw paths in a plot. In order to get multiple lines for a set of data the user must define an
227  * accessor with the name "group" in the config.aes object of the {LABKEY.vis.Plot} or {LABKEY.vis.Layer} object. For
228  * example if the data looked like {x: 12, y: 35, name: "Alan"} the config.aes.group accessor could be "Alan", or a
229  * function: function(row){return row.name}. Each unique name would get a separate line. The geom also supports color
230  * and size aesthetics from the {LABKEY.vis.Plot} and/or {LABKEY.vis.Layer} objects.
231  * @param {Object} config An object with the following properties:
232  * @param {String} [config.color] (Optional) String used to determine the color of all paths. Defaults to black (#000000).
233  * @param {Number} [config.size] (Optional) Number used to determine the size of all paths. Defaults to 3.
234  * @param {Number} [config.opacity] (Optional) Number between 0 and 1, used to determine the opacity of all paths.
235  *                                   Useful if there are many overlapping paths. Defaults to 1.
236  * @param {boolean} [config.dashed] (Optional) True for dashed path, false for solid path. Defaults to false.
237  */
238 LABKEY.vis.Geom.Path = function(config){
239     this.type = "Path";
240     
241     if(!config){
242         config = {};
243     }
244     this.color = ('color' in config && config.color != null && config.color != undefined) ? config.color : '#000000';
245     this.size = ('size' in config && config.size != null && config.size != undefined) ? config.size : 3;
246     this.opacity = ('opacity' in config && config.opacity != null && config.opacity != undefined) ? config.opacity : 1;
247     this.dashed = ('dashed' in config && config.dashed != null && config.dashed != undefined) ? config.dashed : false;
248 
249     return this;
250 };
251 LABKEY.vis.Geom.Path.prototype = new LABKEY.vis.Geom.XY();
252 LABKEY.vis.Geom.Path.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
253     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
254         return false;
255     }
256 
257     this.groupAes = layerAes.group ? layerAes.group : parentAes.group;
258     this.sortFnAes = layerAes.sortFn ? layerAes.sortFn : parentAes.sortFn;
259     this.sizeAes = layerAes.size ? layerAes.size : parentAes.size;
260     this.pathColorAes = layerAes.pathColor ? layerAes.pathColor : parentAes.pathColor;
261     this.sizeScale = scales.size;
262 
263     renderer.renderPathGeom(data, this);
264     return true;
265 };
266 
267 /**
268  * @class Control range geom. Generally used in conjunction with a {@link LABKEY.vis.Geom.Point} and/or {@link LABKEY.vis.Geom.Path}
269  * geom to show upper and lower control range for a given point. In order to work the user must specify an upper or lower accessor
270  * in the config.aes object of the {LABKEY.vis.Plot} or {LABKEY.vis.Layer} object. This Geom also supports the color
271  * aesthetic from the {LABKEY.vis.Plot} and/or {LABKEY.vis.Layer} objects.
272  * @param config An object with the following properties:
273  * @param {String} [config.color] (Optional) String used to determine the color of all paths. Defaults to black (#000000).
274  * @param {Number} [config.size] (Optional) Number used to determine the size of all paths.  Defaults to 2.
275  * @param {Boolean} [config.dashed] (Optional) Whether or not to use dashed lines for path. Defaults to false.
276  * @param {Number} [config.width] (Optional) Number used to determine the length of all paths.  Defaults to 6.
277  */
278 LABKEY.vis.Geom.ControlRange = function(config){
279     this.type = "ControlRange";
280 
281     if(!config){
282         config = {};
283     }
284     this.color = ('color' in config && config.color != null && config.color != undefined) ? config.color : '#000000';
285     this.size = ('size' in config && config.size != null && config.size != undefined) ? config.size : 2;
286     this.dashed = ('dashed' in config && config.dashed != null && config.dashed != undefined) ? config.dashed : false;
287     this.width = ('width' in config && config.width != null && config.width != undefined) ? config.width : 6;
288 
289     return this;
290 };
291 LABKEY.vis.Geom.ControlRange.prototype = new LABKEY.vis.Geom.XY();
292 LABKEY.vis.Geom.ControlRange.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
293     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
294         return false;
295     }
296 
297     this.upperAes = layerAes.upper ? layerAes.upper : parentAes.upper;
298     this.lowerAes = layerAes.lower ? layerAes.lower : parentAes.lower;
299 
300     if (!this.upperAes || !this.lowerAes) {
301         console.error("The upperAes or lowerAes aesthetic is required for the ControlRange geom.");
302         return false;
303     }
304 
305     renderer.renderControlRangeGeom(data, this);
306     return true;
307 };
308 
309 /**
310  * @class Error bar geom. Generally used in conjunction with a {@link LABKEY.vis.Geom.Point} and/or {@link LABKEY.vis.Geom.Path}
311  * geom to show the known amount of error for a given point. In order to work the user must specify an error accessor
312  * in the config.aes object of the {LABKEY.vis.Plot} or {LABKEY.vis.Layer} object. This Geom also supports the color
313  * aesthetic from the {LABKEY.vis.Plot} and/or {LABKEY.vis.Layer} objects.
314  * @param config An object with the following properties:
315  * @param {String} [config.color] (Optional) String used to determine the color of all paths. Defaults to black (#000000).
316  * @param {Number} [config.size] (Optional) Number used to determine the size of all paths.  Defaults to 2.
317  * @param {Boolean} [config.dashed] (Optional) Whether or not to use dashed lines for top and bottom bars. Defaults to false.
318  * @param {String} [config.altColor] (Optional) String used to determine the color of the vertical bar. Defaults to config.color.
319  */
320 LABKEY.vis.Geom.ErrorBar = function(config){
321     this.type = "ErrorBar";
322 
323     if(!config){
324         config = {};
325     }
326     this.color = ('color' in config && config.color != null && config.color != undefined) ? config.color : '#000000';
327     this.size = ('size' in config && config.size != null && config.size != undefined) ? config.size : 2;
328     this.dashed = ('dashed' in config && config.dashed != null && config.dashed != undefined) ? config.dashed : false;
329     this.altColor = ('altColor' in config && config.altColor != null && config.altColor != undefined) ? config.altColor : null;
330     this.width = ('width' in config && config.width != null && config.width != undefined) ? config.width : 6;
331 
332     return this;
333 };
334 LABKEY.vis.Geom.ErrorBar.prototype = new LABKEY.vis.Geom.XY();
335 LABKEY.vis.Geom.ErrorBar.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
336     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
337         return false;
338     }
339 
340     this.errorAes = layerAes.error ? layerAes.error : parentAes.error;
341 
342     if (!this.errorAes) {
343         console.error("The error aesthetic is required for the ErrorBar geom.");
344         return false;
345     }
346 
347     renderer.renderErrorBarGeom(data, this);
348     return true;
349 };
350 
351 
352 /**
353  * @class The Boxplot Geom, used to generate box plots for a given set of data. In order to get multiple box plots for a set of
354  * data with a continuous x-axis scale, the user must define an accessor with the name "group" in the config.aes object
355  * of the {LABKEY.vis.Plot} or {LABKEY.vis.Layer} object. For example if the data looked like {x: 12, y: 35, name: "Alan"}
356  * the config.aes.group accessor could be "Alan", or a function: function(row){return row.name}. Each unique name would
357  * get a separate box plot. If aes.group is not present one boxplot will be generated for all of the data. This geom
358  * also supports the outlierColor, outlierShape, hoverText, outlierHoverText, and pointClickFn aesthetics from the
359  * {LABKEY.vis.Plot} and/or {LABKEY.vis.Layer} objects.
360  *
361  * Boxplots are drawn as follows:
362  * <ul>
363  *      <li>The top line of the box is the first quartile (Q1)</li>
364  *      <li>The middle like is the second quartile (Q2, aka median)</li>
365  *      <li>The bottom line is the third quartile (Q3)</li>
366  *      <li>The whiskers extend to 3/2 times the inner quartile range (Q3 - Q1, aka IQR)</li>
367  *      <li>All data points that are greater than 3/2 times the IQR are drawn as outliers</li>
368  * </ul>
369  *
370  * @param {Object} config An object with the following properties:
371  * @param {String} [config.color] (Optional) A string value used for the line colors in the box plot. Defaults to black
372  *      (#000000)
373  * @param {String} [config.fill] (Optional) A string value used for the fill color in the box plot. Defaults to white
374  *      (#ffffff)
375  * @param {Number} [config.lineWidth] (Optional) A used to set the width of the lines used in the box plot. Defaults to 1.
376  * @param {Number} [config.opacity] (Optional) A number between 0 and 1 used to set the opacity of the box plot. Defaults
377  *      to 1.
378  * @param {String} [config.position] (Optional) A string used to determine how to position the outliers. Currently the
379  *      only possible value is "jitter", which will move the points to the left or right of the center of the box plot by
380  *      a random amount. Defaults to undefined.
381  * @param {Boolean} [config.showOutliers] (Optional) Used to toggle whether or not outliers are rendered. Defaults to true.
382  * @param {String} [config.outlierFill] (Optional) A string value used to set the fill color of the outliers. Defaults
383  *      to black (#000000).
384  * @param {Number} [config.outlierOpacity] (Optional) A number between 0 and 1 used to set the opacity of the outliers.
385  *      Defaults to 1.
386  * @param {Number} [config.outlierSize] (Optional) A used to set the size of outliers. Defaults to 3.
387  */
388 LABKEY.vis.Geom.Boxplot = function(config){
389     this.type = "Boxplot";
390 
391     if(!config){
392         config = {};
393     }
394     this.color = ('color' in config && config.color != null && config.color != undefined) ? config.color : '#000000'; // line color
395     this.fill = ('fill' in config && config.fill != null && config.fill != undefined) ? config.fill : '#ffffff'; // fill color
396     this.lineWidth = ('lineWidth' in config && config.lineWidth != null && config.lineWidth != undefined) ? config.lineWidth : 1;
397     this.opacity = ('opacity' in config && config.opacity != null && config.opacity != undefined) ? config.opacity : 1;
398     this.position = ('position' in config && config.position != null && config.position != undefined) ? config.position : null;
399     this.showOutliers = ('showOutliers' in config && config.showOutliers != null && config.showOutliers != undefined) ? config.showOutliers : true;
400     this.outlierFill = ('outlierFill' in config && config.outlierFill != null && config.outlierFill != undefined) ? config.outlierFill : '#000000';
401     this.outlierOpacity = ('outlierOpacity' in config && config.outlierOpacity != null && config.outlierOpacity != undefined) ? config.outlierOpacity : .5;
402     this.outlierSize = ('outlierSize' in config && config.outlierSize != null && config.outlierSize != undefined) ? config.outlierSize : 3;
403 
404     return this;
405 };
406 LABKEY.vis.Geom.Boxplot.prototype = new LABKEY.vis.Geom.XY();
407 LABKEY.vis.Geom.Boxplot.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
408     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
409         return false;
410     }
411     this.hoverTextAes = layerAes.hoverText ? layerAes.hoverText : parentAes.hoverText;
412     this.pointClickFnAes = layerAes.pointClickFn ? layerAes.pointClickFn : parentAes.pointClickFn;
413     this.groupAes = layerAes.group ? layerAes.group : parentAes.group;
414     this.outlierHoverTextAes = layerAes.outlierHoverText ? layerAes.outlierHoverText : parentAes.outlierHoverText;
415     this.outlierColorAes = layerAes.outlierColor ? layerAes.outlierColor : parentAes.outlierColor;
416     this.outlierShapeAes = layerAes.outlierShape ? layerAes.outlierShape : parentAes.outlierShape;
417     this.shapeScale = scales.shape;
418 
419     renderer.renderBoxPlotGeom(data, this);
420     return true;
421 };
422 
423 LABKEY.vis.Geom.DataspaceBoxPlot = function(config){
424     this.type = "DataspaceBoxplot";
425 
426     if(!config){
427         config = {};
428     }
429 
430     this.color = ('color' in config && config.color != null && config.color != undefined) ? config.color : '#000000'; // line color
431     this.fill = ('fill' in config && config.fill != null && config.fill != undefined) ? config.fill : '#ffffff'; // fill color
432     this.lineWidth = ('lineWidth' in config && config.lineWidth != null && config.lineWidth != undefined) ? config.lineWidth : 1;
433     this.opacity = ('opacity' in config && config.opacity != null && config.opacity != undefined) ? config.opacity : 1;
434     this.showOutliers = ('showOutliers' in config && config.showOutliers != null && config.showOutliers != undefined) ? config.showOutliers : true;
435     this.outlierFill = ('outlierFill' in config && config.outlierFill != null && config.outlierFill != undefined) ? config.outlierFill : '#000000';
436     this.pointOpacity = ('outlierOpacity' in config && config.outlierOpacity != null && config.outlierOpacity != undefined) ? config.outlierOpacity : .5;
437     this.pointSize = ('outlierSize' in config && config.outlierSize != null && config.outlierSize != undefined) ? config.outlierSize : 3;
438     this.size = ('binSize' in config && config.binSize != null && config.binSize != undefined) ? config.binSize : 5;
439 
440     // binning geom specific
441     this.binRowLimit = ('binRowLimit' in config && config.binRowLimit != null && config.binRowLimit != undefined) ? config.binRowLimit : 5000;
442     this.colorRange = ('colorRange' in config && config.colorRange != null && config.colorRange != undefined) ? config.colorRange : ["#e6e6e6", "#000000"]; // lightish-gray -> black
443 
444     return this;
445 };
446 LABKEY.vis.Geom.DataspaceBoxPlot.prototype = new LABKEY.vis.Geom.XY();
447 LABKEY.vis.Geom.DataspaceBoxPlot.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
448     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
449         return false;
450     }
451 
452     this.hoverTextAes = layerAes.hoverText ? layerAes.hoverText : parentAes.hoverText;
453     this.groupAes = layerAes.group ? layerAes.group : parentAes.group;
454     this.pointClickFnAes = layerAes.pointClickFn ? layerAes.pointClickFn : parentAes.pointClickFn;
455     this.pointHoverTextAes = layerAes.pointHoverText ? layerAes.pointHoverText : parentAes.pointHoverText;
456     this.shapeAes = layerAes.shape ? layerAes.shape : parentAes.shape;
457     this.shapeScale = scales.shape;
458     this.sizeAes = layerAes.size ? layerAes.size : parentAes.size;
459     this.sizeScale = scales.size;
460     this.mouseOverFnAes = layerAes.mouseOverFn ? layerAes.mouseOverFn : parentAes.mouseOverFn;
461     this.mouseOutFnAes = layerAes.mouseOutFn ? layerAes.mouseOutFn : parentAes.mouseOutFn;
462     this.mouseUpFnAes = layerAes.mouseUpFn ? layerAes.mouseUpFn : parentAes.mouseUpFn;
463     this.boxMouseOverFnAes = layerAes.boxMouseOverFn ? layerAes.boxMouseOverFn : parentAes.boxMouseOverFn;
464     this.boxMouseOutFnAes = layerAes.boxMouseOutFn ? layerAes.boxMouseOutFn : parentAes.boxMouseOutFn;
465     this.boxMouseUpFnAes = layerAes.boxMouseUpFn ? layerAes.boxMouseUpFn : parentAes.boxMouseUpFn;
466 
467     renderer.renderDataspaceBoxPlotGeom(data, this);
468     return true;
469 };
470 
471 /**
472  * @class Bar plot geom, used to generate bar plots for a given set of data.
473  * @param config An object with the following properties:
474  * @param {Function} [config.clickFn] (Optional) A click function
475  * @param {Function} [config.hoverFn] (Optional) A hover function
476  * @param {String} [config.color] (Optional) A string value used for the line colors in the bar plot. Defaults to black (#000000)
477  * @param {String} [config.fill] (Optional) A string value used for the fill color in the bar plot. Defaults to white (#ffffff)
478  * @param {Number} [config.lineWidth] (Optional) A used to set the width of the lines used in the bar plot. Defaults to 1.
479  * @param {Number} [config.opacity] (Optional) A number between 0 and 1 used to set the opacity of the bar plot. Defaults to 1.
480  * @param {Boolean} [config.showCumulativeTotals] (Optional) True to show cumulative totals next to the individual bars.
481  * @param {String} [config.colorTotal] (Optional) A string value used for the line colors in the cumulative bar plot. Defaults to black (#000000)
482  * @param {String} [config.fillTotal] (Optional) A string value used for the fill color in the cumulative bar plot. Defaults to black (#000000)
483  * @param {Number} [config.lineWidthTotal] (Optional) A used to set the width of the lines used in the cumulative bar plot. Defaults to 1.
484  * @param {Number} [config.opacityTotal] (Optional) A number between 0 and 1 used to set the opacity of the cumulative bar plot. Defaults to 1.
485  * @param {Boolean} [config.showValues] (Optional) True to show the bar height values as text above the rendered bar.
486  */
487 LABKEY.vis.Geom.BarPlot = function(config){
488     this.type = "Barplot";
489 
490     if(!config){
491         config = {};
492     }
493     this.clickFn = ('clickFn' in config && config.clickFn != null && config.clickFn != undefined) ? config.clickFn : undefined;
494     this.hoverFn = ('hoverFn' in config && config.hoverFn != null && config.hoverFn != undefined) ? config.hoverFn : undefined;
495     this.color = ('color' in config && config.color != null && config.color != undefined) ? config.color : '#000000';
496     this.colorTotal = ('colorTotal' in config && config.colorTotal != null && config.colorTotal != undefined) ? config.colorTotal : '#000000';
497     this.fill = ('fill' in config && config.fill != null && config.fill != undefined) ? config.fill : '#c0c0c0';
498     this.fillTotal = ('fillTotal' in config && config.fillTotal != null && config.fillTotal != undefined) ? config.fillTotal : '#000000';
499     this.lineWidth = ('lineWidth' in config && config.lineWidth != null && config.lineWidth != undefined) ? config.lineWidth : 1;
500     this.lineWidthTotal = ('lineWidthTotal' in config && config.lineWidthTotal != null && config.lineWidthTotal != undefined) ? config.lineWidthTotal : 1;
501     this.opacity = ('opacity' in config && config.opacity != null && config.opacity != undefined) ? config.opacity : 1;
502     this.opacityTotal = ('opacityTotal' in config && config.opacityTotal != null && config.opacityTotal != undefined) ? config.opacityTotal : 1;
503     this.showCumulativeTotals = ('showCumulativeTotals' in config && config.showCumulativeTotals != null && config.showCumulativeTotals != undefined) ? config.showCumulativeTotals : false;
504     this.showValues = ('showValues' in config && config.showValues != null && config.showValues != undefined) ? config.showValues : false;
505 
506     return this;
507 };
508 LABKEY.vis.Geom.BarPlot.prototype = new LABKEY.vis.Geom.XY();
509 LABKEY.vis.Geom.BarPlot.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
510 
511     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
512         return false;
513     }
514 
515     renderer.renderBarPlotGeom(data, this);
516     return true;
517 };
518 
519 /**
520  * @class Timeline plot geom, used to generate a timeline plot for a given set of data.
521  * @param config An object with the following properties:
522  * @param {String} [config.size] (Optional) A numeric value used for the timeline event icon size in pixels. Defaults to 10.
523  * @param {String} [config.color] (Optional) A string value used for the timeline event icon border color. Defaults to black (#000000).
524  * @param {String} [config.fill] (Optional) A string value used for the timeline event icon fill color. Defaults to black (#000000).
525  * @param {String} [config.dateKey] (Optional) property name of the date value that data objects contain. Used to create
526  *                  tooltips on hover. Defaults to 'date'.
527  * @param {Boolean} [config.isCollapsed] (Optional) If true, the timeline collapses subtypes into their parent rows. Defaults to True.
528  * @param {Number} [config.rowHeight] (Optional) The height of individual rows in pixels. For expanded timelines, row height
529  *                  will resize to 75% of this value. Defaults to 40px.
530  * @param {Object} [config.highlight] (Optional) Special Data object containing information to highlight a specific row
531  *                  in the timeline. Must have the same shape & properties as all other input data.
532  * @param {String} [config.highlightRowColor] (Optional) Hex color to specify what color the highlighted row will be if,
533  *                  found in the data. Defaults to #74B0C4.
534  * @param {String} [config.activeEventKey] (Optional) Name of property that is paired with @param config.activeEventIdentifier to
535  *                  identify a unique event in the data.
536  * @param {String} [config.activeEventIdentifier] (Optional) Name of value that is paired with @param config.activeEventKey
537  *                  to identify a unique event in the data.
538  * @param {String} [config.activeEventStrokeColor] (Optional) Hex color to specify what color the active event rect's
539  *                  stroke will be, if found in the data. Defaults to red.
540  * @param {Object} [config.emphasisEvents] (Optional) Object containing key:[value] pairs whose keys are property names
541  *                  of a data object and whose value is an array of possible values that should have a highlight line drawn
542  *                  on the chart when found. Example: {'type': ['death', 'Withdrawal']}
543  * @param {String} [config.tickColor] (Optional) Hex color to specify the color of Axis ticks. Defaults to #DDDDDD.
544  * @param {String} [config.emphasisTickColor] (Optional) Hex color to specify the color of emphasis event ticks, if
545  *                  found in the data. Defaults to #1a969d.
546  * @param {String} [config.timeUnit] (Optional) Unit of time to use when calculating how far an event's date is from
547  *                  the start date. Default is years. Valid string values include minutes, hours, days, years, and decades.
548  * @param {Number} [config.eventIconSize] (Optional) Size of event square width/height dimensions.
549  * @param {String} [config.eventIconColor] (Optional) Hex color of event square stroke.
550  * @param {String} [config.eventIconFill] (Optional) Hex color of event square inner fill. Defaults to black (#000000).
551  * @param {Number} [config.eventIconOpacity] (Optional) Float between 0 - 1 (inclusive) to specify how transparent the
552  *                  fill of event icons will be. Defaults to 1.
553  * @param {Array} [config.rowColorDomain] (Optional) Array of length 2 containing string Hex values for the two
554  *                  alternating colors of timeline row rectangles. Defaults to ['#f2f2f2', '#ffffff'].
555  */
556 LABKEY.vis.Geom.TimelinePlot = function(config){
557     this.type = "TimelinePlot";
558 
559     if(!config){
560         config = {};
561     }
562 
563     this.dateKey = ('dateKey' in config && config.dateKey != null && config.dateKey != undefined) ? config.dateKey : 'date';
564     this.timeUnit = ('timeUnit' in config && config.timeUnit != null && config.timeUnit != undefined) ? config.timeUnit : 'years';
565     this.highlight = ('highlight' in config && config.highlight != null && config.highlight != undefined) ? config.highlight : null;
566     this.highlightRowColor = ('highlightRowColor' in config && config.highlightRowColor != null && config.highlightRowColor != undefined) ? config.highlightRowColor : '#74B0C4';
567     this.activeEventKey = ('activeEventKey' in config && config.activeEventKey != null && config.activeEventKey != undefined) ? config.activeEventKey : null;
568     this.activeEventIdentifier = ('activeEventIdentifier' in config && config.activeEventIdentifier != null && config.activeEventIdentifier != undefined) ? config.activeEventIdentifier : null;
569     this.activeEventStrokeColor = ('activeEventStrokeColor' in config && config.activeEventStrokeColor != null && config.activeEventStrokeColor != undefined) ? config.activeEventStrokeColor : 'red';
570     this.marginLeft = ('marginLeft' in config && config.marginLeft != null && config.marginLeft != undefined) ? config.marginLeft : 200;
571     this.parentName = ('parentName' in config && config.parentName != null && config.parentName != undefined) ? config.parentName : 'type';
572     this.childName = ('childName' in config  && config.childName != null && config.childName != undefined) ? config.childName : 'subtype';
573     this.width = ('width' in config && config.width != null && config.width != undefined) ? config.width : 900;
574     this.height = ('height' in config && config.height != null && config.height != undefined) ? config.height : 500;
575     this.rowHeight = ('rowHeight' in config && config.rowHeight != null && config.rowHeight != undefined) ? config.rowHeight : 40;
576     this.eventIconSize = ('eventIconSize' in config && config.eventIconSize != null && config.eventIconSize != undefined) ? config.eventIconSize : 10;
577     this.eventIconColor = ('eventIconColor' in config && config.eventIconColor != null && config.eventIconColor != undefined) ? config.eventIconColor : '#000000';
578     this.eventIconFill = ('eventIconFill' in config && config.eventIconFill != null && config.eventIconFill != undefined) ? config.eventIconFill : '#000000';
579     this.rowColorDomain = ('rowColorDomain' in config && config.rowColorDomain != null && config.rowColorDomain != undefined) ? config.rowColorDomain : ['#f2f2f2', '#ffffff'];
580     this.eventIconOpacity = ('eventIconOpacity' in config && config.eventIconOpacity != null && config.eventIconOpacity != undefined) ? config.eventIconOpacity : 1;
581     this.emphasisEvents = ('emphasisEvents' in config && config.emphasisEvents != null && config.emphasisEvents != undefined) ? config.emphasisEvents : null;
582     this.tickColor = ('tickColor' in config && config.tickColor != null && config.tickColor != undefined) ? config.tickColor : '#DDDDDD';
583     this.emphasisTickColor = ('emphasisTickColor' in config && config.emphasisTickColor != null && config.emphasisTickColor != undefined) ? config.emphasisTickColor : '#1a969d';
584     this.isCollapsed = ('isCollapsed' in config && config.isCollapsed != null && config.isCollapsed != undefined) ? config.isCollapsed : true;
585     return this;
586 };
587 LABKEY.vis.Geom.TimelinePlot.prototype = new LABKEY.vis.Geom.XY();
588 LABKEY.vis.Geom.TimelinePlot.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
589 
590     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
591         return false;
592     }
593 
594     this.mouseOverRowFnAes = layerAes.mouseOverRowFn ? layerAes.mouseOverRowFn : parentAes.mouseOverRowFn;
595     this.mouseOutRowFnAes = layerAes.mouseOutRowFn ? layerAes.mouseOutRowFn : parentAes.mouseOutRowFn;
596     this.rowClickFnAes = layerAes.rowClickFn ? layerAes.rowClickFn : parentAes.rowClickFn;
597     this.eventClickFnAes = layerAes.eventIconClickFn ? layerAes.eventIconClickFn : parentAes.eventIconClickFn;
598     this.mouseOverFnAes = layerAes.mouseOverEventIconFn ? layerAes.mouseOverEventIconFn : parentAes.mouseOverEventIconFn;
599     this.mouseOutFnAes = layerAes.mouseOutEventIconFn ? layerAes.mouseOutEventIconFn : parentAes.mouseOutEventIconFn;
600 
601     if (renderer.renderTimelinePlotGeom)
602     {
603         renderer.renderTimelinePlotGeom(data, this);
604         return true;
605     }
606     else
607     {
608         return false;
609     }
610 };