docs: def getLayout(kind=None,theme=None,title='',xTitle='',yTitle='',zTitle='',barmode='',bargap=None,bargroupgap=None,
  			  margin=None, dimensions=None, width=None, height=None,
  			  annotations=None,is3d=False,**kwargs):
  	"""
  	Generates a plotly Layout
  
  	Parameters:
  	-----------
  		theme : string
  			Layout Theme
  				solar
  				pearl
  				white
  		title : string
  			Chart Title
  		xTitle : string
  			X Axis Title
  		yTitle : string
  			Y Axis Title
  		zTitle : string
  			Z Axis Title
  			Applicable only for 3d charts
  		barmode : string
  			Mode when displaying bars
  				group
  				stack
  				overlay
  		bargap : float
  			Sets the gap between bars
  				[0,1)
  			Applicabe for bar and histogram plots
  		bargroupgap : float
  			Set the gap between groups
  				[0,1)
  			Applicabe for bar and histogram plots
  		gridcolor : string
  				grid color
  		zerolinecolor : string
  				zero line color
  		margin : dict or tuple
  				Dictionary (l,r,b,t) or
  				Tuple containing the left,
  				right, bottom and top margins
  		dimensions : tuple
  			Dimensions of figure
  		annotations : dict or list
  			Dictionary of annotations
  				{x_point : text}
  			or
  			List of Plotly Annotations
  		is3d : bool
  			Indicates if the layout is for a 3D chart
  
  		Other Kwargs
  		============
  
  		Shapes
  			hline : int, list or dict
  				Draws a horizontal line at the
  				indicated y position(s)
  				Extra parameters can be passed in
  				the form of a dictionary (see shapes)
  			vline : int, list or dict
  				Draws a vertical line at the
  				indicated x position(s)
  				Extra parameters can be passed in
  				the form of a dictionary (see shapes)
  			hspan : (y0,y1)
  				Draws a horizontal rectangle at the
  				indicated (y0,y1) positions.
  				Extra parameters can be passed in
  				the form of a dictionary (see shapes)
  			vspan : (x0,x1)
  				Draws a vertical rectangle at the
  				indicated (x0,x1) positions.
  				Extra parameters can be passed in
  				the form of a dictionary (see shapes)
  			shapes : dict or list(dict)
  				List of dictionaries with the
  				specifications of a given shape.
  				See help(cufflinks.tools.get_shape)
  				for more information
  
  		Axis Ranges
  			xrange : [lower_bound,upper_bound]
  				Sets the range for the x axis
  			yrange : [lower_bound,upper_bound]
  				Sets the range for the y axis
  			zrange : [lower_bound,upper_bound]
  				Sets the range for the z axis
  
  		Explicit Layout Updates
  			layout_update : dict
  				The layout will be modified with all
  				the explicit values stated in the
  				dictionary
  
  		Range Selector
  			rangeselector : dict
  				Defines a rangeselector object
  				see help(cf.tools.get_range_selector) for more information
  				Example:
  					{'steps':['1y','2 months','5 weeks','ytd','2mtd'],
  					 'axis':'xaxis', 'bgcolor' : ('blue',.3),
  					 'x': 0.2 , 'y' : 0.9}
  
  		Range Slider
  			rangeslider : bool or dict
  				Defines if a rangeslider is displayed
  				If bool:
  					True : Makes it visible
  				if dict:
  					Rangeslider object
  				Example:
  					{'bgcolor':('blue',.3),'autorange':True}
  
  		Annotations
  			fontcolor : str
  				Text color for annotations
  			fontsize : int
  				Text size for annotations
  			textangle : int
  				Textt angle
  			See https://plot.ly/python/reference/#layout-annotations
  			for a complete list of valid parameters.
  	"""
  
  
  	for key in list(kwargs.keys()):
  		if key not in __LAYOUT_KWARGS:
  			raise Exception("Invalid keyword : '{0}'".format(key))
  
  	if not theme:
  		theme = auth.get_config_file()['theme']
  
  	theme_data = getTheme(theme)
  	layout=theme_data['layout']
  	layout['xaxis'].update({'title':xTitle})
  	layout['yaxis'].update({'title':yTitle})
  
  	fontfamily=kwargs.pop('fontfamily',None)
  	if fontfamily:
  		deep_update(layout,{'font':{'family':fontfamily}})
  
  
  	if barmode:
  		layout.update({'barmode':barmode})
  	if bargroupgap:
  		layout.update({'bargroupgap':bargroupgap})
  	if bargap:
  		layout.update(bargap=bargap)
  	if title:
  		layout.update({'title':title})
  	if annotations:
  		layout.update({'annotations':annotations})
  
  
  	def update_axis(layout,axis='xy',**vals):
  		for _x in axis:
  			for k,v in list(vals.items()):
  				if v==None:
  					vals.pop(k)
  			for k in layout:
  				if '{0}{1}'.format(_x,'axis') in k:
  					layout[k].update(**vals)
  		return layout
  
  	axis_kwargs=check_kwargs(kwargs,__LAYOUT_AXIS,{},True)
  	xaxis_kwargs=kwargs_from_keyword(kwargs,{},'xaxis',True)
  	yaxis_kwargs=kwargs_from_keyword(kwargs,{},'yaxis',True)
  
  	for _x,_vals in (('xy',axis_kwargs),('x',xaxis_kwargs),('y',yaxis_kwargs)):
  		layout=update_axis(layout,_x,**_vals)
  
  	if margin:
  		if isinstance(margin,dict):
  			margin=margin
  		else:
  			margin=dict(list(zip(('l','r','b','t'),margin)))
  		layout.update(margin=margin)
  
  	if dimensions:
  		layout.update(width=dimensions[0])
  		layout.update(height=dimensions[1])
  
  	if height:
  		layout.update(height=height)
  	if width:
  		layout.update(width=width)
  	if is3d:
  		if '3d' in theme_data:
  			layout=deep_update(layout,theme_data['3d'])
  		zaxis=layout['xaxis'].copy()
  		zaxis.update(title=zTitle)
  		scene=dict(xaxis=layout['xaxis'].copy(),yaxis=layout['yaxis'].copy(),zaxis=zaxis)
  		layout.update(scene=scene)
  		del layout['xaxis']
  		del layout['yaxis']
  
  	## Axis Range
  	for r in ['x','y','z']:
  		if '{0}range'.format(r) in kwargs:
  			if is3d:
  				layout['scene']['{0}axis'.format(r)].update(range=kwargs['{0}range'.format(r)])
  			else:
  				layout['{0}axis'.format(r)].update(range=kwargs['{0}range'.format(r)])
  
  	# Need to update this for an add_axis approach.
  	if kind in ('candlestick','ohlc','candle'):
  		layout['yaxis2']=layout['yaxis'].copy()
  		layout['yaxis'].update(showticklabels=False)
  
  	## Kwargs
  
  	if 'legend' in kwargs:
  		if type(kwargs['legend'])==bool:
  			layout['showlegend']=kwargs['legend']
  		elif type(kwargs['legend'])==str:
  			if kwargs['legend']=='top':
  				layout['legend'].update(orientation='h',yanchor='bottom',x=.3,y=.95)
  			elif kwargs['legend']=='bottom':
  				layout['legend'].update(orientation='h',yanchor='bottom',x=.3,y=-0.5)
  			layout['showlegend']=True
  		else:
  			layout['legend']=kwargs['legend']
  			layout['showlegend']=True
  
  	if 'showlegend' in kwargs:
  		layout['showlegend']=kwargs['showlegend']
  
  	# Logarithmic Axis
  	for _ in ['x','y','z']:
  		if 'log{0}'.format(_) in kwargs:
  			if is3d:
  				if kwargs['log{0}'.format(_)]:
  					layout['scene']['{0}axis'.format(_)]['type']='log'
  			else:
  				if kwargs['log{0}'.format(_)]:
  					layout['{0}axis'.format(_)]['type']='log'
  
  	# Shapes
  
  	if any(k in kwargs for k in ['vline','hline','shapes','hspan','vspan']):
  		shapes=[]
  
  		def get_shapes(xline):
  
  			orientation=xline[0]
  			xline=kwargs[xline]
  			if isinstance(xline,list):
  				for x_i in xline:
  					if isinstance(x_i,dict):
  						x_i['kind']='line'
  						shapes.append(get_shape(**x_i))
  					else:
  						if orientation=='h':
  							shapes.append(get_shape(kind='line',y=x_i))
  						else:
  							shapes.append(get_shape(kind='line',x=x_i))
  			elif isinstance(xline,dict):
  				shapes.append(get_shape(**xline))
  			else:
  				if orientation=='h':
  					shapes.append(get_shape(kind='line',y=xline))
  				else:
  					shapes.append(get_shape(kind='line',x=xline))
  
  		def get_span(xspan):
  			orientation=xspan[0]
  			xspan=kwargs[xspan]
  			if isinstance(xspan,list):
  				for x_i in xspan:
  					if isinstance(x_i,dict):
  						x_i['kind']='rect'
  						shapes.append(get_shape(**x_i))
  					else:
  						v0,v1=x_i
  						if orientation=='h':
  							shapes.append(get_shape(kind='rect',y0=v0,y1=v1,fill=True,opacity=.5))
  						else:
  							shapes.append(get_shape(kind='rect',x0=v0,x1=v1,fill=True,opacity=.5))
  			elif isinstance(xspan,dict):
  				xspan['kind']='rect'
  				shapes.append(get_shape(**xspan))
  			elif isinstance(xspan,tuple):
  				v0,v1=xspan
  				if orientation=='h':
  					shapes.append(get_shape(kind='rect',y0=v0,y1=v1,fill=True,opacity=.5))
  				else:
  					shapes.append(get_shape(kind='rect',x0=v0,x1=v1,fill=True,opacity=.5))
  			else:
  				raise Exception('Invalid value for {0}span: {1}'.format(orientation,xspan))
  
  		if 'hline' in kwargs:
  			get_shapes('hline')
  		if 'vline' in kwargs:
  			get_shapes('vline')
  		if 'hspan' in kwargs:
  			get_span('hspan')
  		if 'vspan' in kwargs:
  			get_span('vspan')
  		if 'shapes' in kwargs:
  			shapes_=kwargs['shapes']
  			if isinstance(shapes_,list):
  				for i in shapes_:
  					shp=i if 'type' in i else get_shape(**i)
  					shapes.append(shp)
  			elif isinstance(shapes_,dict):
  					shp=shapes_ if 'type' in shapes_ else get_shape(**shapes_)
  					shapes.append(shp)
  			else:
  				raise Exception("Shapes need to be either a dict or list of dicts")
  
  
  		layout['shapes']=shapes
  
  	# Maps
  	if kind in ('choropleth','scattergeo'):
  		kw=check_kwargs(kwargs,__GEO_KWARGS)
  		defaults={'projection':{'type':'equirectangular'},'showframe':False,'showcoastlines':False}
  		for k,v in list(defaults.items()):
  			if k not in kw:
  				kw[k]=v
  		kw_=kwargs_from_keyword(kw,{},'projection')
  		deep_update(kw,kw_)
  		layout['geo']=kw
  		del layout['xaxis']
  		del layout['yaxis']
  		if not margin:
  			layout['margin']={'autoexpand':True}
  
  	# Range Selector
  	if 'rangeselector' in kwargs:
  		rs=kwargs['rangeselector']
  		if 'axis' in rs:
  			axis=rs['axis']
  			del rs['axis']
  		else:
  			axis='xaxis'
  		layout[axis]['rangeselector']=get_range_selector(**rs)
  
  	# Range Slider
  	if 'rangeslider' in kwargs:
  		if type(kwargs['rangeslider'])==bool:
  			if kwargs['rangeslider']:
  				layout['xaxis']['rangeslider']=dict(visible=kwargs['rangeslider'])
  			else:
  				layout['xaxis']['rangeslider']=dict(visible=False)
  				# layout['yaxis1'].update(domain=(0,0))
  		else:
  			layout['xaxis']['rangeslider']=kwargs['rangeslider']
  	else:
  		if kind in ('ohlc','candle','candlestick'):
  			layout['xaxis']['rangeslider']=dict(visible=False)
  			# layout['yaxis1'].update(domain=(0,0))
  
  
  
  	# Explicit Updates
  
  	if 'layout_update' in kwargs:
  		layout=deep_update(layout,kwargs['layout_update'])
  
  	return layout