1 /*
  2  * Copyright (c) 2012-2016 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 
184     renderer.renderPointGeom(data, this);
185     return true;
186 };
187 
188 /**
189  * @private
190  * @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.
191  * @param config
192  * @returns {LABKEY.vis.Geom.Bin}
193  * @constructor
194  */
195 LABKEY.vis.Geom.Bin = function(config) {
196     this.type = "Bin";
197 
198     if (!config) {
199         config = {};
200     }
201 
202     this.shape = ('shape' in config && config.shape != null && config.shape != undefined) ? config.shape : 'hex';
203     this.colorDomain = ('colorDomain' in config && config.colorDomain != null && config.colorDomain != undefined) ? config.colorDomain : undefined;
204     this.colorRange = ('colorRange' in config && config.colorRange != null && config.colorRange != undefined) ? config.colorRange : ["#e6e6e6", "#085D90"]; // lightish-gray -> labkey blue
205     this.size = ('size' in config && config.size != null && config.size != undefined) ? config.size : 5;
206     this.plotNullPoints = ('plotNullPoints' in config && config.plotNullPoints != null && config.plotNullPoints != undefined) ? config.plotNullPoints : false;
207 
208     return this;
209 };
210 LABKEY.vis.Geom.Bin.prototype = new LABKEY.vis.Geom.XY();
211 LABKEY.vis.Geom.Bin.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
212     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
213         return false;
214     }
215 
216     this.mouseOverFnAes = layerAes.mouseOverFn ? layerAes.mouseOverFn : parentAes.mouseOverFn;
217     this.mouseOutFnAes = layerAes.mouseOutFn ? layerAes.mouseOutFn : parentAes.mouseOutFn;
218     this.mouseUpFnAes = layerAes.mouseUpFn ? layerAes.mouseUpFn : parentAes.mouseUpFn;
219 
220     renderer.renderBinGeom(data, this);
221     return true;
222 };
223 
224 /**
225  * @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
226  * accessor with the name "group" in the config.aes object of the {LABKEY.vis.Plot} or {LABKEY.vis.Layer} object. For
227  * example if the data looked like {x: 12, y: 35, name: "Alan"} the config.aes.group accessor could be "Alan", or a
228  * function: function(row){return row.name}. Each unique name would get a separate line. The geom also supports color
229  * and size aesthetics from the {LABKEY.vis.Plot} and/or {LABKEY.vis.Layer} objects.
230  * @param {Object} config An object with the following properties:
231  * @param {String} [config.color] (Optional) String used to determine the color of all paths. Defaults to black (#000000).
232  * @param {Number} [config.size] (Optional) Number used to determine the size of all paths.  Defaults to 3.
233  * @param {Number} [config.opacity] (Optional) Number between 0 and 1, used to determine the opacity of all paths. Useful
234  * @param {boolean} [config.dashed] (Optional) True for dashed path, false for solid path. Defaults to false.
235  *      if there are many overlapping paths. Defaults to 1.
236  */
237 LABKEY.vis.Geom.Path = function(config){
238     this.type = "Path";
239     
240     if(!config){
241         config = {};
242     }
243     this.color = ('color' in config && config.color != null && config.color != undefined) ? config.color : '#000000';
244     this.size = ('size' in config && config.size != null && config.size != undefined) ? config.size : 3;
245     this.opacity = ('opacity' in config && config.opacity != null && config.opacity != undefined) ? config.opacity : 1;
246     this.dashed = ('dashed' in config && config.dashed != null && config.dashed != undefined) ? config.dashed : false;
247 
248     return this;
249 };
250 LABKEY.vis.Geom.Path.prototype = new LABKEY.vis.Geom.XY();
251 LABKEY.vis.Geom.Path.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
252     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
253         return false;
254     }
255 
256     this.groupAes = layerAes.group ? layerAes.group : parentAes.group;
257     this.sizeAes = layerAes.size ? layerAes.size : parentAes.size;
258     this.pathColorAes = layerAes.pathColor ? layerAes.pathColor : parentAes.pathColor;
259     this.sizeScale = scales.size;
260 
261     renderer.renderPathGeom(data, this);
262     return true;
263 };
264 
265 /**
266  * @class Control range geom. Generally used in conjunction with a {@link LABKEY.vis.Geom.Point} and/or {@link LABKEY.vis.Geom.Path}
267  * 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
268  * in the config.aes object of the {LABKEY.vis.Plot} or {LABKEY.vis.Layer} object. This Geom also supports the color
269  * aesthetic from the {LABKEY.vis.Plot} and/or {LABKEY.vis.Layer} objects.
270  * @param config An object with the following properties:
271  * @param {String} [config.color] (Optional) String used to determine the color of all paths. Defaults to black (#000000).
272  * @param {Number} [config.size] (Optional) Number used to determine the size of all paths.  Defaults to 2.
273  * @param {Boolean} [config.dashed] (Optional) Whether or not to use dashed lines for path. Defaults to false.
274  * @param {Number} [config.width] (Optional) Number used to determine the length of all paths.  Defaults to 6.
275  */
276 LABKEY.vis.Geom.ControlRange = function(config){
277     this.type = "ControlRange";
278 
279     if(!config){
280         config = {};
281     }
282     this.color = ('color' in config && config.color != null && config.color != undefined) ? config.color : '#000000';
283     this.size = ('size' in config && config.size != null && config.size != undefined) ? config.size : 2;
284     this.dashed = ('dashed' in config && config.dashed != null && config.dashed != undefined) ? config.dashed : false;
285     this.width = ('width' in config && config.width != null && config.width != undefined) ? config.width : 6;
286 
287     return this;
288 };
289 LABKEY.vis.Geom.ControlRange.prototype = new LABKEY.vis.Geom.XY();
290 LABKEY.vis.Geom.ControlRange.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
291     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
292         return false;
293     }
294 
295     this.upperAes = layerAes.upper ? layerAes.upper : parentAes.upper;
296     this.lowerAes = layerAes.lower ? layerAes.lower : parentAes.lower;
297 
298     if (!this.upperAes || !this.lowerAes) {
299         console.error("The upperAes or lowerAes aesthetic is required for the ControlRange geom.");
300         return false;
301     }
302 
303     renderer.renderControlRangeGeom(data, this);
304     return true;
305 };
306 
307 /**
308  * @class Error bar geom. Generally used in conjunction with a {@link LABKEY.vis.Geom.Point} and/or {@link LABKEY.vis.Geom.Path}
309  * geom to show the known amount of error for a given point. In order to work the user must specify an error accessor
310  * in the config.aes object of the {LABKEY.vis.Plot} or {LABKEY.vis.Layer} object. This Geom also supports the color
311  * aesthetic from the {LABKEY.vis.Plot} and/or {LABKEY.vis.Layer} objects.
312  * @param config An object with the following properties:
313  * @param {String} [config.color] (Optional) String used to determine the color of all paths. Defaults to black (#000000).
314  * @param {Number} [config.size] (Optional) Number used to determine the size of all paths.  Defaults to 2.
315  * @param {Boolean} [config.dashed] (Optional) Whether or not to use dashed lines for top and bottom bars. Defaults to false.
316  * @param {String} [config.altColor] (Optional) String used to determine the color of the vertical bar. Defaults to config.color.
317  */
318 LABKEY.vis.Geom.ErrorBar = function(config){
319     this.type = "ErrorBar";
320 
321     if(!config){
322         config = {};
323     }
324     this.color = ('color' in config && config.color != null && config.color != undefined) ? config.color : '#000000';
325     this.size = ('size' in config && config.size != null && config.size != undefined) ? config.size : 2;
326     this.dashed = ('dashed' in config && config.dashed != null && config.dashed != undefined) ? config.dashed : false;
327     this.altColor = ('altColor' in config && config.altColor != null && config.altColor != undefined) ? config.altColor : null;
328     this.width = ('width' in config && config.width != null && config.width != undefined) ? config.width : 6;
329 
330     return this;
331 };
332 LABKEY.vis.Geom.ErrorBar.prototype = new LABKEY.vis.Geom.XY();
333 LABKEY.vis.Geom.ErrorBar.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
334     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
335         return false;
336     }
337 
338     this.errorAes = layerAes.error ? layerAes.error : parentAes.error;
339 
340     if (!this.errorAes) {
341         console.error("The error aesthetic is required for the ErrorBar geom.");
342         return false;
343     }
344 
345     renderer.renderErrorBarGeom(data, this);
346     return true;
347 };
348 
349 
350 /**
351  * @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
352  * data with a continuous x-axis scale, the user must define an accessor with the name "group" in the config.aes object
353  * of the {LABKEY.vis.Plot} or {LABKEY.vis.Layer} object. For example if the data looked like {x: 12, y: 35, name: "Alan"}
354  * the config.aes.group accessor could be "Alan", or a function: function(row){return row.name}. Each unique name would
355  * get a separate box plot. If aes.group is not present one boxplot will be generated for all of the data. This geom
356  * also supports the outlierColor, outlierShape, hoverText, outlierHoverText, and pointClickFn aesthetics from the
357  * {LABKEY.vis.Plot} and/or {LABKEY.vis.Layer} objects.
358  *
359  * Boxplots are drawn as follows:
360  * <ul>
361  *      <li>The top line of the box is the first quartile (Q1)</li>
362  *      <li>The middle like is the second quartile (Q2, aka median)</li>
363  *      <li>The bottom line is the third quartile (Q3)</li>
364  *      <li>The whiskers extend to 3/2 times the inner quartile range (Q3 - Q1, aka IQR)</li>
365  *      <li>All data points that are greater than 3/2 times the IQR are drawn as outliers</li>
366  * </ul>
367  *
368  * @param {Object} config An object with the following properties:
369  * @param {String} [config.color] (Optional) A string value used for the line colors in the box plot. Defaults to black
370  *      (#000000)
371  * @param {String} [config.fill] (Optional) A string value used for the fill color in the box plot. Defaults to white
372  *      (#ffffff)
373  * @param {Number} [config.lineWidth] (Optional) A used to set the width of the lines used in the box plot. Defaults to 1.
374  * @param {Number} [config.opacity] (Optional) A number between 0 and 1 used to set the opacity of the box plot. Defaults
375  *      to 1.
376  * @param {String} [config.position] (Optional) A string used to determine how to position the outliers. Currently the
377  *      only possible value is "jitter", which will move the points to the left or right of the center of the box plot by
378  *      a random amount. Defaults to undefined.
379  * @param {Boolean} [config.showOutliers] (Optional) Used to toggle whether or not outliers are rendered. Defaults to true.
380  * @param {String} [config.outlierFill] (Optional) A string value used to set the fill color of the outliers. Defaults
381  *      to black (#000000).
382  * @param {Number} [config.outlierOpacity] (Optional) A number between 0 and 1 used to set the opacity of the outliers.
383  *      Defaults to 1.
384  * @param {Number} [config.outlierSize] (Optional) A used to set the size of outliers. Defaults to 3.
385  */
386 LABKEY.vis.Geom.Boxplot = function(config){
387     this.type = "Boxplot";
388 
389     if(!config){
390         config = {};
391     }
392     this.color = ('color' in config && config.color != null && config.color != undefined) ? config.color : '#000000'; // line color
393     this.fill = ('fill' in config && config.fill != null && config.fill != undefined) ? config.fill : '#ffffff'; // fill color
394     this.lineWidth = ('lineWidth' in config && config.lineWidth != null && config.lineWidth != undefined) ? config.lineWidth : 1;
395     this.opacity = ('opacity' in config && config.opacity != null && config.opacity != undefined) ? config.opacity : 1;
396     this.position = ('position' in config && config.position != null && config.position != undefined) ? config.position : null;
397     this.showOutliers = ('showOutliers' in config && config.showOutliers != null && config.showOutliers != undefined) ? config.showOutliers : true;
398     this.outlierFill = ('outlierFill' in config && config.outlierFill != null && config.outlierFill != undefined) ? config.outlierFill : '#000000';
399     this.outlierOpacity = ('outlierOpacity' in config && config.outlierOpacity != null && config.outlierOpacity != undefined) ? config.outlierOpacity : .5;
400     this.outlierSize = ('outlierSize' in config && config.outlierSize != null && config.outlierSize != undefined) ? config.outlierSize : 3;
401 
402     return this;
403 };
404 LABKEY.vis.Geom.Boxplot.prototype = new LABKEY.vis.Geom.XY();
405 LABKEY.vis.Geom.Boxplot.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
406     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
407         return false;
408     }
409     this.hoverTextAes = layerAes.hoverText ? layerAes.hoverText : parentAes.hoverText;
410     this.pointClickFnAes = layerAes.pointClickFn ? layerAes.pointClickFn : parentAes.pointClickFn;
411     this.groupAes = layerAes.group ? layerAes.group : parentAes.group;
412     this.outlierHoverTextAes = layerAes.outlierHoverText ? layerAes.outlierHoverText : parentAes.outlierHoverText;
413     this.outlierColorAes = layerAes.outlierColor ? layerAes.outlierColor : parentAes.outlierColor;
414     this.outlierShapeAes = layerAes.outlierShape ? layerAes.outlierShape : parentAes.outlierShape;
415     this.shapeScale = scales.shape;
416 
417     renderer.renderBoxPlotGeom(data, this);
418     return true;
419 };
420 
421 LABKEY.vis.Geom.DataspaceBoxPlot = function(config){
422     this.type = "DataspaceBoxplot";
423 
424     if(!config){
425         config = {};
426     }
427 
428     this.color = ('color' in config && config.color != null && config.color != undefined) ? config.color : '#000000'; // line color
429     this.fill = ('fill' in config && config.fill != null && config.fill != undefined) ? config.fill : '#ffffff'; // fill color
430     this.lineWidth = ('lineWidth' in config && config.lineWidth != null && config.lineWidth != undefined) ? config.lineWidth : 1;
431     this.opacity = ('opacity' in config && config.opacity != null && config.opacity != undefined) ? config.opacity : 1;
432     this.showOutliers = ('showOutliers' in config && config.showOutliers != null && config.showOutliers != undefined) ? config.showOutliers : true;
433     this.outlierFill = ('outlierFill' in config && config.outlierFill != null && config.outlierFill != undefined) ? config.outlierFill : '#000000';
434     this.pointOpacity = ('outlierOpacity' in config && config.outlierOpacity != null && config.outlierOpacity != undefined) ? config.outlierOpacity : .5;
435     this.pointSize = ('outlierSize' in config && config.outlierSize != null && config.outlierSize != undefined) ? config.outlierSize : 3;
436     this.size = ('binSize' in config && config.binSize != null && config.binSize != undefined) ? config.binSize : 5;
437 
438     // binning geom specific
439     this.binRowLimit = ('binRowLimit' in config && config.binRowLimit != null && config.binRowLimit != undefined) ? config.binRowLimit : 5000;
440     this.colorRange = ('colorRange' in config && config.colorRange != null && config.colorRange != undefined) ? config.colorRange : ["#e6e6e6", "#000000"]; // lightish-gray -> black
441 
442     return this;
443 };
444 LABKEY.vis.Geom.DataspaceBoxPlot.prototype = new LABKEY.vis.Geom.XY();
445 LABKEY.vis.Geom.DataspaceBoxPlot.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
446     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
447         return false;
448     }
449 
450     this.hoverTextAes = layerAes.hoverText ? layerAes.hoverText : parentAes.hoverText;
451     this.groupAes = layerAes.group ? layerAes.group : parentAes.group;
452     this.pointClickFnAes = layerAes.pointClickFn ? layerAes.pointClickFn : parentAes.pointClickFn;
453     this.pointHoverTextAes = layerAes.pointHoverText ? layerAes.pointHoverText : parentAes.pointHoverText;
454     this.shapeAes = layerAes.shape ? layerAes.shape : parentAes.shape;
455     this.shapeScale = scales.shape;
456     this.sizeAes = layerAes.size ? layerAes.size : parentAes.size;
457     this.sizeScale = scales.size;
458     this.mouseOverFnAes = layerAes.mouseOverFn ? layerAes.mouseOverFn : parentAes.mouseOverFn;
459     this.mouseOutFnAes = layerAes.mouseOutFn ? layerAes.mouseOutFn : parentAes.mouseOutFn;
460     this.mouseUpFnAes = layerAes.mouseUpFn ? layerAes.mouseUpFn : parentAes.mouseUpFn;
461     this.boxMouseOverFnAes = layerAes.boxMouseOverFn ? layerAes.boxMouseOverFn : parentAes.boxMouseOverFn;
462     this.boxMouseOutFnAes = layerAes.boxMouseOutFn ? layerAes.boxMouseOutFn : parentAes.boxMouseOutFn;
463     this.boxMouseUpFnAes = layerAes.boxMouseUpFn ? layerAes.boxMouseUpFn : parentAes.boxMouseUpFn;
464 
465     renderer.renderDataspaceBoxPlotGeom(data, this);
466     return true;
467 };
468 
469 /**
470  * @class Bar plot geom, used to generate bar plots for a given set of data.
471  * @param config An object with the following properties:
472  * @param {Function} [config.clickFn] (Optional) A click function
473  * @param {Function} [config.hoverFn] (Optional) A hover function
474  * @param {String} [config.color] (Optional) A string value used for the line colors in the bar plot. Defaults to black (#000000)
475  * @param {String} [config.fill] (Optional) A string value used for the fill color in the bar plot. Defaults to white (#ffffff)
476  * @param {Number} [config.lineWidth] (Optional) A used to set the width of the lines used in the bar plot. Defaults to 1.
477  * @param {Number} [config.opacity] (Optional) A number between 0 and 1 used to set the opacity of the bar plot. Defaults to 1.
478  * @param {Boolean} [config.showCumulativeTotals] (Optional) True to show cumulative totals next to the individual bars.
479  * @param {String} [config.colorTotal] (Optional) A string value used for the line colors in the cumulative bar plot. Defaults to black (#000000)
480  * @param {String} [config.fillTotal] (Optional) A string value used for the fill color in the cumulative bar plot. Defaults to black (#000000)
481  * @param {Number} [config.lineWidthTotal] (Optional) A used to set the width of the lines used in the cumulative bar plot. Defaults to 1.
482  * @param {Number} [config.opacityTotal] (Optional) A number between 0 and 1 used to set the opacity of the cumulative bar plot. Defaults to 1.
483  * @param {Boolean} [config.showValues] (Optional) True to show the bar height values as text above the rendered bar.
484  */
485 LABKEY.vis.Geom.BarPlot = function(config){
486     this.type = "Barplot";
487 
488     if(!config){
489         config = {};
490     }
491     this.clickFn = ('clickFn' in config && config.clickFn != null && config.clickFn != undefined) ? config.clickFn : undefined;
492     this.hoverFn = ('hoverFn' in config && config.hoverFn != null && config.hoverFn != undefined) ? config.hoverFn : undefined;
493     this.color = ('color' in config && config.color != null && config.color != undefined) ? config.color : '#000000';
494     this.colorTotal = ('colorTotal' in config && config.colorTotal != null && config.colorTotal != undefined) ? config.colorTotal : '#000000';
495     this.fill = ('fill' in config && config.fill != null && config.fill != undefined) ? config.fill : '#c0c0c0';
496     this.fillTotal = ('fillTotal' in config && config.fillTotal != null && config.fillTotal != undefined) ? config.fillTotal : '#000000';
497     this.lineWidth = ('lineWidth' in config && config.lineWidth != null && config.lineWidth != undefined) ? config.lineWidth : 1;
498     this.lineWidthTotal = ('lineWidthTotal' in config && config.lineWidthTotal != null && config.lineWidthTotal != undefined) ? config.lineWidthTotal : 1;
499     this.opacity = ('opacity' in config && config.opacity != null && config.opacity != undefined) ? config.opacity : 1;
500     this.opacityTotal = ('opacityTotal' in config && config.opacityTotal != null && config.opacityTotal != undefined) ? config.opacityTotal : 1;
501     this.showCumulativeTotals = ('showCumulativeTotals' in config && config.showCumulativeTotals != null && config.showCumulativeTotals != undefined) ? config.showCumulativeTotals : false;
502     this.showValues = ('showValues' in config && config.showValues != null && config.showValues != undefined) ? config.showValues : false;
503 
504     return this;
505 };
506 LABKEY.vis.Geom.BarPlot.prototype = new LABKEY.vis.Geom.XY();
507 LABKEY.vis.Geom.BarPlot.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
508 
509     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
510         return false;
511     }
512 
513     renderer.renderBarPlotGeom(data, this);
514     return true;
515 };
516 
517 /**
518  * @class Timeline plot geom, used to generate a timeline plot for a given set of data.
519  * @param config An object with the following properties:
520  * @param {String} [config.size] (Optional) A numeric value used for the timeline event icon size in pixels. Defaults to 10.
521  * @param {String} [config.color] (Optional) A string value used for the timeline event icon border color. Defaults to black (#000000).
522  * @param {String} [config.fill] (Optional) A string value used for the timeline event icon fill color. Defaults to black (#000000).
523  * @param {String} [config.dateKey] (Optional) property name of the date value that data objects contain. Used to create
524  *                  tooltips on hover. Defaults to 'date'.
525  * @param {Boolean} [config.isCollapsed] (Optional) If true, the timeline collapses subtypes into their parent rows. Defaults to True.
526  * @param {Number} [config.rowHeight] (Optional) The height of individual rows in pixels. For expanded timelines, row height
527  *                  will resize to 75% of this value. Defaults to 40px.
528  * @param {Object} [config.highlight] (Optional) Special Data object containing information to highlight a specific row
529  *                  in the timeline. Must have the same shape & properties as all other input data.
530  * @param {String} [config.highlightRowColor] (Optional) Hex color to specifiy what color the highlighted row will be if,
531  *                  found in the data. Defaults to #74B0C4.
532  * @param {String} [config.activeEventKey] (Optional) Name of property that is paired with @param config.activeEventIdentifier to
533  *                  identify a unique event in the data.
534  * @param {String} [config.activeEventIdentifier] (Optional) Name of value that is paired with @param config.activeEventKey
535  *                  to identify a unique event in the data.
536  * @param {String} [config.activeEventStrokeColor] (Optional) Hex color to specifiy what color the active event rect's
537  *                  stroke will be, if found in the data. Defaults to red.
538  * @param {Object} [config.emphasisEvents] (Optional) Object containing key:[value] pairs whose keys are property names
539  *                  of a data object and whose value is an array of possible values that should have a highlight line drawn
540  *                  on the chart when found. Example: {'type': ['death', 'Withdrawal']}
541  * @param {String} [config.tickColor] (Optional) Hex color to specifiy the color of Axis ticks. Defaults to #DDDDDD.
542  * @param {String} [config.emphasisTickColor] (Optional) Hex color to specify the color of emphasis event ticks, if
543  *                  found in the data. Defaults to #1a969d.
544  * @param {String} [config.timeUnit] (Optional) Unit of time to use when calculating how far an event's date is from
545  *                  the start date. Default is years. Valid string values include minutes, hours, days, years, and decades.
546  * @param {Number} [config.eventIconSize] (Optional) Size of event square width/height dimensions.
547  * @param {String} [config.eventIconColor] (Optional) Hex color of event square stroke.
548  * @param {String} [config.eventIconFill] (Optional) Hex color of event square inner fill. Defaults to black (#000000).
549  * @param {Number} [config.eventIconOpacity] (Optional) Float between 0 - 1 (inclusive) to specify how transparent the
550  *                  fill of event icons will be. Defaults to 1.
551  * @param {Array} [config.rowColorDomain] (Optional) Array of length 2 containing string Hex values for the two
552  *                  alternating colors of timeline row rectangles. Defaults to ['#f2f2f2', '#ffffff'].
553  */
554 LABKEY.vis.Geom.TimelinePlot = function(config){
555     this.type = "TimelinePlot";
556 
557     if(!config){
558         config = {};
559     }
560 
561     this.dateKey = ('dateKey' in config && config.dateKey != null && config.dateKey != undefined) ? config.dateKey : 'date';
562     this.timeUnit = ('timeUnit' in config && config.timeUnit != null && config.timeUnit != undefined) ? config.timeUnit : 'years';
563     this.highlight = ('highlight' in config && config.highlight != null && config.highlight != undefined) ? config.highlight : null;
564     this.highlightRowColor = ('highlightRowColor' in config && config.highlightRowColor != null && config.highlightRowColor != undefined) ? config.highlightRowColor : '#74B0C4';
565     this.activeEventKey = ('activeEventKey' in config && config.activeEventKey != null && config.activeEventKey != undefined) ? config.activeEventKey : null;
566     this.activeEventIdentifier = ('activeEventIdentifier' in config && config.activeEventIdentifier != null && config.activeEventIdentifier != undefined) ? config.activeEventIdentifier : null;
567     this.activeEventStrokeColor = ('activeEventStrokeColor' in config && config.activeEventStrokeColor != null && config.activeEventStrokeColor != undefined) ? config.activeEventStrokeColor : 'red';
568     this.marginLeft = ('marginLeft' in config && config.marginLeft != null && config.marginLeft != undefined) ? config.marginLeft : 200;
569     this.parentName = ('parentName' in config && config.parentName != null && config.parentName != undefined) ? config.parentName : 'type';
570     this.childName = ('childName' in config  && config.childName != null && config.childName != undefined) ? config.childName : 'subtype';
571     this.width = ('width' in config && config.width != null && config.width != undefined) ? config.width : 900;
572     this.height = ('height' in config && config.height != null && config.height != undefined) ? config.height : 500;
573     this.rowHeight = ('rowHeight' in config && config.rowHeight != null && config.rowHeight != undefined) ? config.rowHeight : 40;
574     this.eventIconSize = ('eventIconSize' in config && config.eventIconSize != null && config.eventIconSize != undefined) ? config.eventIconSize : 10;
575     this.eventIconColor = ('eventIconColor' in config && config.eventIconColor != null && config.eventIconColor != undefined) ? config.eventIconColor : '#000000';
576     this.eventIconFill = ('eventIconFill' in config && config.eventIconFill != null && config.eventIconFill != undefined) ? config.eventIconFill : '#000000';
577     this.rowColorDomain = ('rowColorDomain' in config && config.rowColorDomain != null && config.rowColorDomain != undefined) ? config.rowColorDomain : ['#f2f2f2', '#ffffff'];
578     this.eventIconOpacity = ('eventIconOpacity' in config && config.eventIconOpacity != null && config.eventIconOpacity != undefined) ? config.eventIconOpacity : 1;
579     this.emphasisEvents = ('emphasisEvents' in config && config.emphasisEvents != null && config.emphasisEvents != undefined) ? config.emphasisEvents : null;
580     this.tickColor = ('tickColor' in config && config.tickColor != null && config.tickColor != undefined) ? config.tickColor : '#DDDDDD';
581     this.emphasisTickColor = ('emphasisTickColor' in config && config.emphasisTickColor != null && config.emphasisTickColor != undefined) ? config.emphasisTickColor : '#1a969d';
582     this.isCollapsed = ('isCollapsed' in config && config.isCollapsed != null && config.isCollapsed != undefined) ? config.isCollapsed : true;
583     return this;
584 };
585 LABKEY.vis.Geom.TimelinePlot.prototype = new LABKEY.vis.Geom.XY();
586 LABKEY.vis.Geom.TimelinePlot.prototype.render = function(renderer, grid, scales, data, layerAes, parentAes, name, index){
587 
588     if(!this.initAesthetics(scales, layerAes, parentAes, name, index)){
589         return false;
590     }
591 
592     this.mouseOverRowFnAes = layerAes.mouseOverRowFn ? layerAes.mouseOverRowFn : parentAes.mouseOverRowFn;
593     this.mouseOutRowFnAes = layerAes.mouseOutRowFn ? layerAes.mouseOutRowFn : parentAes.mouseOutRowFn;
594     this.rowClickFnAes = layerAes.rowClickFn ? layerAes.rowClickFn : parentAes.rowClickFn;
595     this.eventClickFnAes = layerAes.eventIconClickFn ? layerAes.eventIconClickFn : parentAes.eventIconClickFn;
596     this.mouseOverFnAes = layerAes.mouseOverEventIconFn ? layerAes.mouseOverEventIconFn : parentAes.mouseOverEventIconFn;
597     this.mouseOutFnAes = layerAes.mouseOutEventIconFn ? layerAes.mouseOutEventIconFn : parentAes.mouseOutEventIconFn;
598 
599     if (renderer.renderTimelinePlotGeom)
600     {
601         renderer.renderTimelinePlotGeom(data, this);
602         return true;
603     }
604     else
605     {
606         return false;
607     }
608 };