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