"""Public API for all plots supported by HoloViews, regardless ofplotting package or backend. Every plotting classes must be a subclassof this Plot baseclass."""importuuidimportwarningsfromastimportliteral_evalfromcollectionsimportCounter,defaultdictfromfunctoolsimportpartialfromitertoolsimportgroupby,productimportnumpyasnpimportparamfrompanel.configimportconfigfrompanel.io.documentimportunlockedfrompanel.io.notebookimportpushfrompanel.io.stateimportstatefrompyviz_commsimportJupyterCommfrom..coreimporttraversal,utilfrom..core.dataimportDataset,disable_pipelinefrom..core.elementimportElement,Element3Dfrom..core.layoutimportEmpty,Layout,NdLayoutfrom..core.optionsimportCompositor,SkipRendering,Store,lookup_optionsfrom..core.overlayimportCompositeOverlay,NdOverlay,Overlayfrom..core.spacesimportDynamicMap,HoloMapfrom..core.utilimportisfinite,stream_parametersfrom..elementimportGraph,Tablefrom..selectionimportNoOpSelectionDisplayfrom..streamsimportRangeX,RangeXY,RangeY,Streamfrom..util.transformimportdimfrom.utilimport(attach_streams,compute_overlayable_zorders,dim_axis_label,dynamic_update,get_axis_padding,get_dynamic_mode,get_minimum_span,get_nested_plot_frame,get_nested_streams,get_plot_frame,get_range,initialize_unbounded,scale_fontsize,split_dmap_overlay,traverse_setter,)
[docs]classPlot(param.Parameterized):""" Base class of all Plot classes in HoloViews, designed to be general enough to use any plotting package or backend. """backend=None# A list of style options that may be supplied to the plotting# callstyle_opts=[]# Sometimes matplotlib doesn't support the common aliases.# Use this list to disable any invalid style options_disabled_opts=[]def__init__(self,renderer=None,root=None,**params):params={k:vfork,vinparams.items()ifkinself.param}super().__init__(**params)self.renderer=rendererifrendererelseStore.renderers[self.backend].instance()self._force=Falseself._comm=Noneself._document=Noneself._root=Noneself._pane=Noneself._triggering=[]self._trigger=[]self.set_root(root)@propertydefstate(self):""" The plotting state that gets updated via the update method and used by the renderer to generate output. """raiseNotImplementedError
[docs]defset_root(self,root):""" Sets the root model on all subplots. """ifrootisNone:returnforplotinself.traverse(lambdax:x):plot._root=root
[docs]definitialize_plot(self,ranges=None):""" Initialize the matplotlib figure. """raiseNotImplementedError
[docs]defupdate(self,key):""" Update the internal state of the Plot to represent the given key tuple (where integers represent frames). Returns this state. """returnself.state
[docs]defcleanup(self):""" Cleans up references to the plot on the attached Stream subscribers. """plots=self.traverse(lambdax:x,[Plot])forplotinplots:ifnotisinstance(plot,(GenericCompositePlot,GenericElementPlot,GenericOverlayPlot)):continueforstreaminset(plot.streams):stream._subscribers=[(p,subscriber)forp,subscriberinstream._subscribersifnotutil.is_param_method(subscriber)orutil.get_method_owner(subscriber)notinplots]
[docs]defrefresh(self,**kwargs):""" Refreshes the plot by rerendering it and then pushing the updated data if the plot has an associated Comm. """ifself.renderer.mode=='server'andnotstate._unblocked(self.document):# If we do not have the Document lock, schedule refresh as callbackself._triggering+=[(s,dict(s._metadata))forpinself.traverse(lambdax:x,[Plot])forsingetattr(p,'streams',[])ifs._triggering]ifself.documentandself.document.session_context:self.document.add_next_tick_callback(self.refresh)return# Ensure that server based tick callbacks maintain stream triggering statefors,metadatainself._triggering:s._triggering=Trues._metadata.update(metadata)try:traverse_setter(self,'_force',True)key=self.current_keyifself.current_keyelseself.keys[0]dim_streams=[streamforstreaminself.streamsifany(cinself.dimensionsforcinstream.contents)]stream_params=stream_parameters(dim_streams)key=tuple(Noneifdinstream_paramselsekford,kinzip(self.dimensions,key))stream_key=util.wrap_tuple_streams(key,self.dimensions,self.streams)self._trigger_refresh(stream_key)ifself.top_level:self.push()exceptExceptionase:raiseefinally:# Reset triggering statefors,_inself._triggering:s._triggering=Falses._metadata.clear()self._triggering=[]
def_trigger_refresh(self,key):"Triggers update to a plot on a refresh event"# Update if not top-level, batched or an ElementPlotifnotself.top_levelorisinstance(self,GenericElementPlot):withunlocked():self.update(key)
[docs]defpush(self):""" Pushes plot updates to the frontend. """root=self._rootif(rootandself.paneisnotNoneandroot.ref['id']inself.pane._plots):child_pane=self.pane._plots[root.ref['id']][1]else:child_pane=Noneifself.renderer.backend!='bokeh'andchild_paneisnotNone:child_pane.object=self.renderer.get_plot_state(self)elif(self.renderer.mode!='server'androotand'embedded'notinroot.tagsandself.documentandself.comm):push(self.document,self.comm)
@propertydefid(self):returnself.comm.idifself.commelseid(self.state)def__len__(self):""" Returns the total number of available frames. """raiseNotImplementedError@classmethoddeflookup_options(cls,obj,group):returnlookup_options(obj,group,cls.backend)
[docs]classPlotSelector:""" Proxy that allows dynamic selection of a plotting class based on a function of the plotted object. Behaves like a Plot class and presents the same parameterized interface. """_disabled_opts=[]def__init__(self,selector,plot_classes,allow_mismatch=False):""" The selector function accepts a component instance and returns the appropriate key to index plot_classes dictionary. """self.selector=selectorself.plot_classes=dict(plot_classes)interface=self._define_interface(self.plot_classes.values(),allow_mismatch)self.style_opts,self.plot_options=interfacedefselection_display(self,obj):plt_class=self.get_plot_class(obj)returngetattr(plt_class,'selection_display',None)def_define_interface(self,plots,allow_mismatch):parameters=[{k:v.precedencefork,vinplot.param.objects().items()if((v.precedenceisNone)or(v.precedence>=0))}forplotinplots]param_sets=[set(params.keys())forparamsinparameters]ifnotallow_mismatchandnotall(pset==param_sets[0]forpsetinparam_sets):# Find the mismatching setsmismatching_sets=[psetforpsetinparam_setsifpset!=param_sets[0]]# Print the mismatching setsformismatch_setinmismatching_sets:print("Mismatching plot options:",mismatch_set)raiseException("All selectable plot classes must have identical plot options.")styles=[plot.style_optsforplotinplots]ifnotallow_mismatchandnotall(style==styles[0]forstyleinstyles):raiseException("All selectable plot classes must have identical style options.")plot_params={p:vforparamsinparametersforp,vinparams.items()}return[sforstyleinstylesforsinstyle],plot_paramsdef__call__(self,obj,**kwargs):plot_class=self.get_plot_class(obj)returnplot_class(obj,**kwargs)defget_plot_class(self,obj):key=self.selector(obj)ifkeynotinself.plot_classes:msg="Key %s returned by selector not in set: %s"raiseException(msg%(key,', '.join(self.plot_classes.keys())))returnself.plot_classes[key]def__setattr__(self,label,value):try:returnsuper().__setattr__(label,value)exceptExceptionase:raiseException("Please set class parameters directly on classes %s"%', '.join(str(cls)forclsinself.__dict__['plot_classes'].values()))fromedefparams(self):returnself.plot_options@propertydefparam(self):returnself.plot_options
[docs]classDimensionedPlot(Plot):""" DimensionedPlot implements a number of useful methods to compute dimension ranges and titles containing the dimension values. """fontsize=param.Parameter(default=None,allow_None=True,doc=""" Specifies various font sizes of the displayed text. Finer control is available by supplying a dictionary where any unmentioned keys revert to the default sizes, e.g: {'ticks':20, 'title':15, 'ylabel':5, 'xlabel':5, 'zlabel':5, 'legend':8, 'legend_title':13} You can set the font size of 'zlabel', 'ylabel' and 'xlabel' together using the 'labels' key.""")fontscale=param.Number(default=None,doc=""" Scales the size of all fonts.""")#Allowed fontsize keys_fontsize_keys=['xlabel','ylabel','zlabel','clabel','labels','xticks','yticks','zticks','cticks','ticks','minor_xticks','minor_yticks','minor_ticks','title','legend','legend_title',]show_title=param.Boolean(default=True,doc=""" Whether to display the plot title.""")title=param.String(default="{label}{group}\n{dimensions}",doc=""" The formatting string for the title of this plot, allows defining a label group separator and dimension labels.""")normalize=param.Boolean(default=True,doc=""" Whether to compute ranges across all Elements at this level of plotting. Allows selecting normalization at different levels for nested data containers.""")projection=param.Parameter(default=None,doc=""" Allows supplying a custom projection to transform the axis coordinates during display. Example projections include '3d' and 'polar' projections supported by some backends. Depending on the backend custom, projection objects may be supplied.""")def__init__(self,keys=None,dimensions=None,layout_dimensions=None,uniform=True,subplot=False,adjoined=None,layout_num=0,style=None,subplots=None,dynamic=False,**params):self.subplots=subplotsself.adjoined=adjoinedself.dimensions=dimensionsself.layout_num=layout_numself.layout_dimensions=layout_dimensionsself.subplot=subplotself.keys=keysifkeysisNoneelselist(keys)self.uniform=uniformself.dynamic=dynamicself.drawn=Falseself.handles={}self.group=Noneself.label=Noneself.current_frame=Noneself.current_key=Noneself.ranges={}self._updated=False# Whether the plot should be marked as updatedsuper().__init__(**params)def__getitem__(self,frame):""" Get the state of the Plot for a given frame number. """ifisinstance(frame,int)andframe>len(self):self.param.warning(f"Showing last frame available: {len(self)}")ifnotself.drawn:self.handles['fig']=self.initialize_plot()ifnotisinstance(frame,tuple):frame=self.keys[frame]self.update_frame(frame)returnself.statedef_get_frame(self,key):""" Required on each MPLPlot type to get the data corresponding just to the current frame out from the object. """
[docs]defmatches(self,spec):""" Matches a specification against the current Plot. """ifcallable(spec)andnotisinstance(spec,type):returnspec(self)elifisinstance(spec,type):returnisinstance(self,spec)else:raiseValueError("Matching specs have to be either a type or a callable.")
[docs]deftraverse(self,fn=None,specs=None,full_breadth=True):""" Traverses any nested DimensionedPlot returning a list of all plots that match the specs. The specs should be supplied as a list of either Plot types or callables, which should return a boolean given the plot class. """accumulator=[]matches=specsisNoneifnotmatches:forspecinspecs:matches=self.matches(spec)ifmatches:breakifmatches:accumulator.append(fn(self)iffnelseself)# Assumes composite objects are iterablesifhasattr(self,'subplots')andself.subplots:forelinself.subplots.values():ifelisNone:continueaccumulator+=el.traverse(fn,specs,full_breadth)ifnotfull_breadth:breakreturnaccumulator
def_frame_title(self,key,group_size=2,separator='\n'):""" Returns the formatted dimension group strings for a particular frame. """ifself.layout_dimensionsisnotNone:dimensions,key=zip(*self.layout_dimensions.items())elifnotself.dynamicand(notself.uniformorlen(self)==1)orself.subplot:return''else:key=keyifisinstance(key,tuple)else(key,)dimensions=self.dimensionsdimension_labels=[dim.pprint_value_string(k)fordim,kinzip(dimensions,key)]groups=[', '.join(dimension_labels[i*group_size:(i+1)*group_size])foriinrange(len(dimension_labels))]returnutil.bytes_to_unicode(separator.join(gforgingroupsifg))def_format_title(self,key,dimensions=True,separator='\n'):label,group,type_name,dim_title=self._format_title_components(key,dimensions=True,separator='\n')title=util.bytes_to_unicode(self.title).format(label=util.bytes_to_unicode(label),group=util.bytes_to_unicode(group),type=type_name,dimensions=dim_title)returntitle.strip(' \n')def_format_title_components(self,key,dimensions=True,separator='\n'):""" Determine components of title as used by _format_title method. To be overridden in child classes. Return signature: (label, group, type_name, dim_title) """return(self.label,self.group,type(self).__name__,'')def_get_fontsize_defaults(self):""" Should returns default fontsize for the following keywords: * ticks * minor_ticks * label * title * legend * legend_title However may also provide more specific defaults for specific axis label or ticks, e.g. clabel or xticks. """return{}def_fontsize(self,key,label='fontsize',common=True):ifnotself.fontsizeandnotself.fontscale:return{}elifnotisinstance(self.fontsize,dict)andself.fontsizeisnotNoneandcommon:return{label:scale_fontsize(self.fontsize,self.fontscale)}fontsize=self.fontsizeifisinstance(self.fontsize,dict)else{}unknown_keys=set(fontsize.keys())-set(self._fontsize_keys)ifunknown_keys:msg="Popping unknown keys %r from fontsize dictionary.\nValid keys: %r"self.param.warning(msg%(list(unknown_keys),self._fontsize_keys))forkeyinunknown_keys:fontsize.pop(key,None)defaults=self._get_fontsize_defaults()size=Noneifkeyinfontsize:size=fontsize[key]elifkeyin['zlabel','ylabel','xlabel','clabel']:size=fontsize.get('labels',defaults.get(key,defaults.get('label')))elifkeyin['xticks','yticks','zticks','cticks']:size=fontsize.get('ticks',defaults.get(key,defaults.get('ticks')))elifkeyin['minor_xticks','minor_yticks']:size=fontsize.get('minor_ticks',defaults.get(key,defaults.get('minor_ticks')))elifkeyin('legend','legend_title','title'):size=defaults.get(key)ifsizeisNone:return{}return{label:scale_fontsize(size,self.fontscale)}
[docs]defcompute_ranges(self,obj,key,ranges):""" Given an object, a specific key, and the normalization options, this method will find the specified normalization options on the appropriate OptionTree, group the elements according to the selected normalization option (i.e. either per frame or over the whole animation) and finally compute the dimension ranges in each group. The new set of ranges is returned. """prev_frame=getattr(self,'prev_frame',None)all_table=all(isinstance(el,Table)forelinobj.traverse(lambdax:x,[Element]))ifobjisNoneornotself.normalizeorall_table:return{}# Get inherited rangesranges=self.rangesifrangesisNoneelse{k:dict(v)fork,vinranges.items()}# Get element identifiers from current object and resolve# with selected normalization optionsnorm_opts=self._get_norm_opts(obj)# Traverse displayed object if normalization applies# at this level, and ranges for the group have not# been supplied from a composite plotreturn_fn=lambdax:xifisinstance(x,Element)elseNoneforgroup,(axiswise,framewise,robust)innorm_opts.items():axiswise=(notgetattr(self,'shared_axes',True))or(axiswise)elements=[]# Skip if ranges are cached or already computed by a# higher-level container object.framewise=framewiseorself.dynamicorlen(elements)==1ifnotframewise:# Traverse to get all elementselements=obj.traverse(return_fn,[group])elifkeyisnotNone:# Traverse to get elements for each frameframe=self._get_frame(key)elements=[]ifframeisNoneelseframe.traverse(return_fn,[group])# Only compute ranges if not axiswise on a composite plot# or not framewise on a Overlay or ElementPlotif(not(axiswiseandnotisinstance(obj,HoloMap))or(notframewiseandisinstance(obj,HoloMap))):self._compute_group_range(group,elements,ranges,framewise,axiswise,robust,self.top_level,prev_frame)self.ranges.update(ranges)returnranges
def_get_norm_opts(self,obj):""" Gets the normalization options for a LabelledData object by traversing the object to find elements and their ids. The id is then used to select the appropriate OptionsTree, accumulating the normalization options into a dictionary. Returns a dictionary of normalization options for each element in the tree. """norm_opts={}# Get all elements' type.group.label specs and idstype_val_fn=lambdax:(x.id,(type(x).__name__,util.group_sanitizer(x.group,escape=False),util.label_sanitizer(x.label,escape=False))) \
ifisinstance(x,Element)elseNoneelement_specs={(idspec[0],idspec[1])foridspecinobj.traverse(type_val_fn)ifidspecisnotNone}# Group elements specs by ID and override normalization# options sequentiallykey_fn=lambdax:-1ifx[0]isNoneelsex[0]id_groups=groupby(sorted(element_specs,key=key_fn),key_fn)forgid,element_spec_groupinid_groups:gid=Noneifgid==-1elsegidgroup_specs=[elfor_,elinelement_spec_group]backend=self.renderer.backendoptstree=Store.custom_options(backend=backend).get(gid,Store.options(backend=backend))# Get the normalization options for the current id# and match against customizable elementsforoptsinoptstree:path=tuple(opts.path.split('.')[1:])applies=any(path==spec[:i]forspecingroup_specsforiinrange(1,4))ifappliesand'norm'inopts.groups:nopts=opts['norm'].optionspopts=opts['plot'].optionsif'axiswise'innoptsor'framewise'innoptsor'clim_percentile'inpopts:norm_opts.update({path:(nopts.get('axiswise',False),nopts.get('framewise',False),popts.get('clim_percentile',False))})element_specs=[specfor_,specinelement_specs]norm_opts.update({spec:(False,False,False)forspecinelement_specsifnotany(spec[:i]innorm_opts.keys()foriinrange(1,4))})returnnorm_opts@classmethoddef_merge_group_ranges(cls,ranges):hard_range=util.max_range(ranges['hard'],combined=False)soft_range=util.max_range(ranges['soft'])robust_range=util.max_range(ranges.get('robust',[]))data_range=util.max_range(ranges['data'])combined=util.dimension_range(data_range[0],data_range[1],hard_range,soft_range)dranges={'data':data_range,'hard':hard_range,'soft':soft_range,'combined':combined,'robust':robust_range,'values':ranges}if'factors'inranges:all_factors=ranges['factors']factor_dtypes={fs.dtypeforfsinall_factors}ifall_factorselse[]dtype=next(iter(factor_dtypes))iflen(factor_dtypes)==1elseNoneexpanded=[vforfctrsinall_factorsforvinfctrs]ifdtypeisnotNone:try:# Try to keep the same dtypeexpanded=np.array(expanded,dtype=dtype)exceptException:passdranges['factors']=util.unique_array(expanded)returndranges@classmethoddef_compute_group_range(cls,group,elements,ranges,framewise,axiswise,robust,top_level,prev_frame):# Iterate over all elements in a normalization group# and accumulate their ranges into the supplied dictionary.elements=[elforelinelementsifelisnotNone]data_ranges={}robust_ranges={}categorical_dims=[]forelinelements:forel_diminel.dimensions('ranges'):ifhasattr(el,'interface'):ifisinstance(el,Graph)andel_diminel.nodes.dimensions():dtype=el.nodes.interface.dtype(el.nodes,el_dim)else:dtype=el.interface.dtype(el,el_dim)elifhasattr(el,'__len__')andlen(el):dtype=el.dimension_values(el_dim).dtypeelse:dtype=Noneifall(util.isfinite(r)forrinel_dim.range):data_range=(None,None)elifdtypeisnotNoneanddtype.kindin'SU':data_range=('','')elifisinstance(el,Graph)andel_diminel.kdims[:2]:data_range=el.nodes.range(2,dimension_range=False)elifel_dim.values:ds=Dataset(el_dim.values,el_dim)data_range=ds.range(el_dim,dimension_range=False)else:data_range=el.range(el_dim,dimension_range=False)data_ranges[(el,el_dim)]=data_rangeifdtypeisnotNoneanddtype.kindin'uif'androbust:percentile=2ifisinstance(robust,bool)elserobustrobust_ranges[(el,el_dim)]=(dim(el_dim,np.nanpercentile,percentile).apply(el),dim(el_dim,np.nanpercentile,100-percentile).apply(el))if(any(isinstance(r,str)forrindata_range)or(el_dim.typeisnotNoneandissubclass(el_dim.type,str))or(dtypeisnotNoneanddtype.kindin'SU')):categorical_dims.append(el_dim)prev_ranges=ranges.get(group,{})group_ranges={}forelinelements:ifisinstance(el,(Empty,Table)):continueopts=cls.lookup_options(el,'style')plot_opts=cls.lookup_options(el,'plot')opt_kwargs=dict(opts.kwargs,**plot_opts.kwargs)ifnotopt_kwargs.get('apply_ranges',True):continue# Compute normalization for color dim transformsfork,vinopt_kwargs.items():ifnotisinstance(v,dim)or('color'notinkandk!='magnitude'):continueifisinstance(v,dim)andv.applies(el):dim_name=repr(v)ifdim_nameinprev_rangesandnotframewise:continuevalues=v.apply(el,all_values=True)factors=Noneifvalues.dtype.kind=='M':drange=values.min(),values.max()elifutil.isscalar(values):drange=values,valueselifvalues.dtype.kindin'US':factors=util.unique_array(values)eliflen(values)==0:drange=np.nan,np.nanelse:try:withwarnings.catch_warnings():warnings.filterwarnings('ignore',r'All-NaN (slice|axis) encountered')drange=(np.nanmin(values),np.nanmax(values))exceptException:factors=util.unique_array(values)ifdim_namenotingroup_ranges:group_ranges[dim_name]={'id':[],'data':[],'hard':[],'soft':[]}iffactorsisnotNone:if'factors'notingroup_ranges[dim_name]:group_ranges[dim_name]['factors']=[]group_ranges[dim_name]['factors'].append(factors)else:group_ranges[dim_name]['data'].append(drange)group_ranges[dim_name]['id'].append(id(el))# Compute dimension normalizationforel_diminel.dimensions('ranges'):dim_name=el_dim.nameifdim_nameinprev_rangesandnotframewise:continuedata_range=data_ranges[(el,el_dim)]ifdim_namenotingroup_ranges:group_ranges[dim_name]={'id':[],'data':[],'hard':[],'soft':[],'robust':[]}group_ranges[dim_name]['data'].append(data_range)group_ranges[dim_name]['hard'].append(el_dim.range)group_ranges[dim_name]['soft'].append(el_dim.soft_range)if(el,el_dim)inrobust_ranges:group_ranges[dim_name]['robust'].append(robust_ranges[(el,el_dim)])ifel_dimincategorical_dims:if'factors'notingroup_ranges[dim_name]:group_ranges[dim_name]['factors']=[]ifel_dim.valuesnotin([],None):values=el_dim.valueselifel_diminel:ifisinstance(el,Graph)andel_diminel.kdims[:2]:# Graph start/end normalization should include all node indicesvalues=el.nodes.dimension_values(2,expanded=False)else:values=el.dimension_values(el_dim,expanded=False)elifisinstance(el,Graph)andel_diminel.nodes:values=el.nodes.dimension_values(el_dim,expanded=False)if(isinstance(values,np.ndarray)andvalues.dtype.kind=='O'andall(isinstance(v,(np.ndarray))forvinvalues)):values=np.concatenate(values)iflen(values)else[]factors=util.unique_array(values)group_ranges[dim_name]['factors'].append(factors)group_ranges[dim_name]['id'].append(id(el))# Avoid merging ranges with non-matching typesgroup_dim_ranges=defaultdict(dict)forgdim,valuesingroup_ranges.items():matching=Truefort,rsinvalues.items():iftin('factors','id'):continuematching&=(len({'date'ifisinstance(v,util.datetime_types)else'number'forrnginrsforvinrngifutil.isfinite(v)})<2)ifmatching:group_dim_ranges[gdim]=values# Merge ranges across elementsdim_ranges=[]forgdim,valuesingroup_dim_ranges.items():dranges=cls._merge_group_ranges(values)dim_ranges.append((gdim,dranges))# Merge local ranges into global range dictionaryifprev_rangesandnot(top_leveloraxiswise)andframewiseandprev_frameisnotNone:# Partially update global ranges with local changesprev_ids=prev_frame.traverse(lambdao:id(o))ford,drangesindim_ranges:values=prev_ranges.get(d,{}).get('values',None)ifvaluesisNoneor'id'notinvalues:forg,drangeindranges.items():ifdnotinprev_ranges:prev_ranges[d]={}prev_ranges[d][g]=drangecontinueids=values.get('id')# Filter out ranges of updated elements and append new rangesmerged={}forg,drangeindranges['values'].items():filtered=[rfori,rinzip(ids,values[g])ifinotinprev_ids]filtered+=drangemerged[g]=filteredprev_ranges[d]=cls._merge_group_ranges(merged)elifprev_rangesandnot(framewiseand(top_leveloraxiswise)):# Combine local with global rangeford,drangesindim_ranges:forg,drangeindranges.items():prange=prev_ranges.get(d,{}).get(g,None)ifprangeisNone:ifdnotinprev_ranges:prev_ranges[d]={}prev_ranges[d][g]=drangeelifgin('factors','values'):prev_ranges[d][g]=drangeelse:prev_ranges[d][g]=util.max_range([prange,drange],combined=g=='hard')else:# Override global rangeranges[group]=dict(dim_ranges)@classmethoddef_traverse_options(cls,obj,opt_type,opts,specs=None,keyfn=None,defaults=True):""" Traverses the supplied object getting all options in opts for the specified opt_type and specs. Also takes into account the plotting class defaults for plot options. If a keyfn is supplied the returned options will be grouped by the returned keys. """deflookup(x):""" Looks up options for object, including plot defaults. keyfn determines returned key otherwise None key is used. """options=cls.lookup_options(x,opt_type)selected={o:options.options[o]foroinoptsifoinoptions.options}ifopt_type=='plot'anddefaults:plot=Store.registry[cls.backend].get(type(x))selected['defaults']={o:getattr(plot,o)foroinoptsifonotinselectedandhasattr(plot,o)}key=keyfn(x)ifkeyfnelseNonereturn(key,selected)# Traverse object and accumulate options by keytraversed=obj.traverse(lookup,specs)options={}default_opts=defaultdict(lambda:defaultdict(list))forkey,optsintraversed:defaults=opts.pop('defaults',{})ifkeynotinoptions:options[key]={}foropt,vinopts.items():ifoptnotinoptions[key]:options[key][opt]=[]options[key][opt].append(v)foropt,vindefaults.items():default_opts[key][opt].append(v)# Merge defaults into dictionary if not explicitly specifiedforkey,optsindefault_opts.items():foropt,vinopts.items():ifoptnotinoptions[key]:options[key][opt]=vreturnoptionsifkeyfnelseoptions[None]def_get_projection(cls,obj):""" Uses traversal to find the appropriate projection for a nested object. Respects projections set on Overlays before considering Element based settings, before finally looking up the default projection on the plot type. If more than one non-None projection type is found an exception is raised. """isoverlay=lambdax:isinstance(x,CompositeOverlay)element3d=obj.traverse(lambdax:x,[Element3D])ifelement3d:return'3d'opts=cls._traverse_options(obj,'plot',['projection'],[CompositeOverlay,Element],keyfn=isoverlay)from_overlay=notall(pisNoneforpinopts.get(True,{}).get('projection',[]))projections=opts.get(from_overlay,{}).get('projection',[])custom_projs=[pforpinprojectionsifpisnotNone]iflen(set(custom_projs))>1:raiseValueError("An axis may only be assigned one projection type")returncustom_projs[0]ifcustom_projselseNone
def__len__(self):""" Returns the total number of available frames. """returnlen(self.keys)
classCallbackPlot:backend=Nonedef_construct_callbacks(self):""" Initializes any callbacks for streams which have defined the plotted object as a source. """source_streams=[]cb_classes=set()registry=list(Stream.registry.items())callbacks=Stream._callbacks[self.backend]forsourceinself.link_sources:streams=[sforsrc,streamsinregistryforsinstreamsifsrcissourceor(src._plot_idisnotNoneandsrc._plot_id==source._plot_id)]cb_classes|={(callbacks[type(stream)],stream)forstreaminstreamsiftype(stream)incallbacksandstream.linkedandstream.sourceisnotNone}cbs=[]sorted_cbs=sorted(cb_classes,key=lambdax:id(x[0]))forcb,groupingroupby(sorted_cbs,lambdax:x[0]):cb_streams=[sfor_,singroup]forcb_streamincb_streams:ifcb_streamnotinsource_streams:source_streams.append(cb_stream)cbs.append(cb(self,cb_streams,source))returncbs,source_streams@propertydeflink_sources(self):"Returns potential Link or Stream sources."ifisinstance(self,GenericOverlayPlot):zorders=[]elifself.batched:zorders=list(range(self.zorder,self.zorder+len(self.hmap.last)))else:zorders=[self.zorder]ifisinstance(self,GenericOverlayPlot)andnotself.batched:sources=[self.hmap.last]elifnotself.staticorisinstance(self.hmap,DynamicMap):sources=[ofori,inputsinself.stream_sources.items()foroininputsifiinzorders]else:sources=[self.hmap.last]returnsources
[docs]classGenericElementPlot(DimensionedPlot):""" Plotting baseclass to render contents of an Element. Implements methods to get the correct frame given a HoloMap, axis labels and extents and titles. """apply_ranges=param.Boolean(default=True,doc=""" Whether to compute the plot bounds from the data itself.""")apply_extents=param.Boolean(default=True,doc=""" Whether to apply extent overrides on the Elements""")bgcolor=param.ClassSelector(class_=(str,tuple),default=None,doc=""" If set bgcolor overrides the background color of the axis.""")default_span=param.ClassSelector(default=2.0,class_=(int,float,tuple),doc=""" Defines the span of an axis if the axis range is zero, i.e. if the lower and upper end of an axis are equal or no range is defined at all. For example if there is a single datapoint at 0 a default_span of 2.0 will result in axis ranges spanning from -1 to 1.""")hooks=param.HookList(default=[],doc=""" Optional list of hooks called when finalizing a plot. The hook is passed the plot object and the displayed element, and other plotting handles can be accessed via plot.handles.""")invert_axes=param.Boolean(default=False,doc=""" Whether to invert the x- and y-axis""")invert_xaxis=param.Boolean(default=False,doc=""" Whether to invert the plot x-axis.""")invert_yaxis=param.Boolean(default=False,doc=""" Whether to invert the plot y-axis.""")logx=param.Boolean(default=False,doc=""" Whether the x-axis of the plot will be a log axis.""")logy=param.Boolean(default=False,doc=""" Whether the y-axis of the plot will be a log axis.""")padding=param.ClassSelector(default=0.1,class_=(int,float,tuple),doc=""" Fraction by which to increase auto-ranged extents to make datapoints more visible around borders. To compute padding, the axis whose screen size is largest is chosen, and the range of that axis is increased by the specified fraction along each axis. Other axes are then padded ensuring that the amount of screen space devoted to padding is equal for all axes. If specified as a tuple, the int or float values in the tuple will be used for padding in each axis, in order (x,y or x,y,z). For example, for padding=0.2 on a 800x800-pixel plot, an x-axis with the range [0,10] will be padded by 20% to be [-1,11], while a y-axis with a range [0,1000] will be padded to be [-100,1100], which should make the padding be approximately the same number of pixels. But if the same plot is changed to have a height of only 200, the y-range will then be [-400,1400] so that the y-axis padding will still match that of the x-axis. It is also possible to declare non-equal padding value for the lower and upper bound of an axis by supplying nested tuples, e.g. padding=(0.1, (0, 0.1)) will pad the x-axis lower and upper bound as well as the y-axis upper bound by a fraction of 0.1 while the y-axis lower bound is not padded at all.""")show_legend=param.Boolean(default=True,doc=""" Whether to show legend for the plot.""")show_grid=param.Boolean(default=False,doc=""" Whether to show a Cartesian grid on the plot.""")xaxis=param.ObjectSelector(default='bottom',objects=['top','bottom','bare','top-bare','bottom-bare',None,True,False],doc=""" Whether and where to display the xaxis. The "bare" options allow suppressing all axis labels, including ticks and xlabel. Valid options are 'top', 'bottom', 'bare', 'top-bare' and 'bottom-bare'.""")yaxis=param.ObjectSelector(default='left',objects=['left','right','bare','left-bare','right-bare',None,True,False],doc=""" Whether and where to display the yaxis. The "bare" options allow suppressing all axis labels, including ticks and ylabel. Valid options are 'left', 'right', 'bare', 'left-bare' and 'right-bare'.""")xlabel=param.String(default=None,doc=""" An explicit override of the x-axis label, if set takes precedence over the dimension label.""")ylabel=param.String(default=None,doc=""" An explicit override of the y-axis label, if set takes precedence over the dimension label.""")xlim=param.Tuple(default=(np.nan,np.nan),length=2,doc=""" User-specified x-axis range limits for the plot, as a tuple (low,high). If specified, takes precedence over data and dimension ranges.""")ylim=param.Tuple(default=(np.nan,np.nan),length=2,doc=""" User-specified x-axis range limits for the plot, as a tuple (low,high). If specified, takes precedence over data and dimension ranges.""")zlim=param.Tuple(default=(np.nan,np.nan),length=2,doc=""" User-specified z-axis range limits for the plot, as a tuple (low,high). If specified, takes precedence over data and dimension ranges.""")xrotation=param.Integer(default=None,bounds=(0,360),doc=""" Rotation angle of the xticks.""")yrotation=param.Integer(default=None,bounds=(0,360),doc=""" Rotation angle of the yticks.""")xticks=param.Parameter(default=None,doc=""" Ticks along x-axis specified as an integer, explicit list of tick locations, or bokeh Ticker object. If set to None default bokeh ticking behavior is applied.""")yticks=param.Parameter(default=None,doc=""" Ticks along y-axis specified as an integer, explicit list of tick locations, or bokeh Ticker object. If set to None default bokeh ticking behavior is applied.""")# A dictionary mapping of the plot methods used to draw the# glyphs corresponding to the ElementPlot, can support two# keyword arguments a 'single' implementation to draw an individual# plot and a 'batched' method to draw multiple Elements at once_plot_methods={}# Declares the options that are propagated from sub-elements of the# plot, mostly useful for inheriting options from individual# Elements on an OverlayPlot. Enabled by default in v1.7._propagate_options=[]v17_option_propagation=True_deprecations={'color_index':("The `color_index` parameter is deprecated in favor of color ""style mapping, e.g. `color=dim('color')` or `line_color=dim('color')`"),'size_index':("The `size_index` parameter is deprecated in favor of size ""style mapping, e.g. `size=dim('size')**2`."),'scaling_method':("The `scaling_method` parameter is deprecated in favor of size ""style mapping, e.g. `size=dim('size')**2` for area scaling."),'scaling_factor':("The `scaling_factor` parameter is deprecated in favor of size ""style mapping, e.g. `size=dim('size')*10`."),'size_fn':("The `size_fn` parameter is deprecated in favor of size ""style mapping, e.g. `size=abs(dim('size'))`."),}_selection_display=NoOpSelectionDisplay()_multi_y_propagation=Falsedef__init__(self,element,keys=None,ranges=None,dimensions=None,batched=False,overlaid=0,cyclic_index=0,zorder=0,style=None,overlay_dims=None,stream_sources=None,streams=None,**params):ifstream_sourcesisNone:stream_sources={}ifoverlay_dimsisNone:overlay_dims={}self.zorder=zorderself.cyclic_index=cyclic_indexself.overlaid=overlaidself.overlay_dims=overlay_dimsifnotisinstance(element,(HoloMap,DynamicMap)):self.hmap=HoloMap(initial_items=(0,element),kdims=['Frame'],id=element.id)else:self.hmap=elementifoverlaid:self.stream_sources=stream_sourceselse:self.stream_sources=compute_overlayable_zorders(self.hmap)plot_element=self.hmap.lastifbatchedandnotisinstance(self,GenericOverlayPlot):plot_element=plot_element.lastdynamic=isinstance(element,DynamicMap)andnotelement.unboundedself.top_level=keysisNoneifself.top_level:dimensions=self.hmap.kdimskeys=list(self.hmap.data.keys())self.style=self.lookup_options(plot_element,'style')ifstyleisNoneelsestyleplot_opts=self.lookup_options(plot_element,'plot').optionspropagate_options=self._propagate_options.copy()ifself._multi_y_propagation:propagate_options=list(set(propagate_options)-set(GenericOverlayPlot._multi_y_unpropagated))ifself.v17_option_propagation:inherited=self._traverse_options(plot_element,'plot',propagate_options,defaults=False)plot_opts.update(**{k:v[0]fork,vininherited.items()ifknotinplot_opts})applied_params=dict(params,**plot_opts)forp,pvalinapplied_params.items():ifpinself.paramandpinself._deprecationsandpvalisnotNone:self.param.warning(self._deprecations[p])super().__init__(keys=keys,dimensions=dimensions,dynamic=dynamic,**applied_params)self.batched=batchedself.streams=get_nested_streams(self.hmap)ifstreamsisNoneelsestreams# Attach streams if not overlaid and not a batched ElementPlotifnot(self.overlaidor(self.batchedandnotisinstance(self,GenericOverlayPlot))):attach_streams(self,self.hmap)# Update plot and style options for batched plotsifself.batched:self.ordering=util.layer_sort(self.hmap)overlay_opts=self.lookup_options(self.hmap.last,'plot').options.items()opts={k:vfork,vinoverlay_optsifkinself.param}self.param.update(**opts)self.style=self.lookup_options(plot_element,'style').max_cycles(len(self.ordering))else:self.ordering=[]
[docs]defget_zorder(self,overlay,key,el):""" Computes the z-order of element in the NdOverlay taking into account possible batching of elements. """spec=util.get_overlay_spec(overlay,key,el)returnself.ordering.index(spec)
def_updated_zorders(self,overlay):specs=[util.get_overlay_spec(overlay,key,el)forkey,elinoverlay.data.items()]self.ordering=sorted(set(self.ordering+specs))return[self.ordering.index(spec)forspecinspecs]def_get_axis_dims(self,element):""" Returns the dimensions corresponding to each axis. Should return a list of dimensions or list of lists of dimensions, which will be formatted to label the axis and to link axes. """dims=element.dimensions()[:2]iflen(dims)==1:returndims+[None,None]else:returndims+[None]def_has_axis_dimension(self,element,dimension):dims=self._get_axis_dims(element)returnany(dimensionindsifisinstance(ds,list)elsedimension==dsfordsindims)def_get_frame(self,key):ifisinstance(self.hmap,DynamicMap)andself.overlaidandself.current_frame:self.current_key=keyreturnself.current_frameelifkey==self.current_keyandnotself._force:returnself.current_framecached=self.current_keyisNoneandnotany(s._triggeringforsinself.streams)key_map=dict(zip([d.namefordinself.dimensions],key))frame=get_plot_frame(self.hmap,key_map,cached)traverse_setter(self,'_force',False)ifkeynotinself.keysandlen(key)==self.hmap.ndimsandself.dynamic:self.keys.append(key)self.current_frame=frameself.current_key=keyreturnframedef_execute_hooks(self,element):""" Executes finalize hooks """forhookinself.hooks:try:hook(self,element)exceptExceptionase:self.param.warning(f"Plotting hook {hook!r} could not be "f"applied:\n\n{e}")
[docs]defget_aspect(self,xspan,yspan):""" Should define the aspect ratio of the plot. """
[docs]defget_padding(self,obj,extents):""" Computes padding along the axes taking into account the plot aspect. """(x0,y0,z0,x1,y1,z1)=extentspadding_opt=self.lookup_options(obj,'plot').kwargs.get('padding')ifself.overlaid:padding=0elifpadding_optisNone:ifself.param.objects('existing')['padding'].defaultisnotself.padding:padding=self.paddingelse:opts=self._traverse_options(obj,'plot',['padding'],specs=[Element],defaults=True)padding=opts.get('padding')ifpadding:padding=padding[0]else:padding=self.paddingelse:padding=padding_optxpad,ypad,zpad=get_axis_padding(padding)ifnotself.overlaidandnotself.batched:xspan=x1-x0ifutil.is_number(x0)andutil.is_number(x1)elseNoneyspan=y1-y0ifutil.is_number(y0)andutil.is_number(y1)elseNoneaspect=self.get_aspect(xspan,yspan)ifaspect>1:xpad=tuple(xp/aspectforxpinxpad)ifisinstance(xpad,tuple)elsexpad/aspectelse:ypad=tuple(yp*aspectforypinypad)ifisinstance(ypad,tuple)elseypad*aspectreturnxpad,ypad,zpad
[docs]defget_extents(self,element,ranges,range_type='combined',dimension=None,xdim=None,ydim=None,zdim=None,**kwargs):""" Gets the extents for the axes from the current Element. The globally computed ranges can optionally override the extents. The extents are computed by combining the data ranges, extents and dimension ranges. Each of these can be obtained individually by setting the range_type to one of: * 'data': Just the data ranges * 'extents': Element.extents * 'soft': Dimension.soft_range values * 'hard': Dimension.range values To obtain the combined range, which includes range padding the default may be used: * 'combined': All the range types combined and padding applied This allows Overlay plots to obtain each range and combine them appropriately for all the objects in the overlay. """num=6if(isinstance(self.projection,str)andself.projection=='3d')else4ifself.apply_extentsandrange_typein('combined','extents'):norm_opts=self.lookup_options(element,'norm').optionsifnorm_opts.get('framewise',False)orself.dynamic:extents=element.extentselse:extent_list=self.hmap.traverse(lambdax:x.extents,[Element])extents=util.max_extents(extent_list,isinstance(self.projection,str)andself.projection=='3d')else:extents=(np.nan,)*numifrange_type=='extents':returnextentsifself.apply_ranges:range_extents=self._get_range_extents(element,ranges,range_type,xdim,ydim,zdim)else:range_extents=(np.nan,)*numifgetattr(self,'shared_axes',False)andself.subplot:combined=util.max_extents([range_extents,extents],isinstance(self.projection,str)andself.projection=='3d')else:max_extent=[]forl1,l2inzip(range_extents,extents):ifisfinite(l2):max_extent.append(l2)else:max_extent.append(l1)combined=tuple(max_extent)ifisinstance(self.projection,str)andself.projection=='3d':x0,y0,z0,x1,y1,z1=combinedelse:x0,y0,x1,y1=combinedx0,x1=util.dimension_range(x0,x1,self.xlim,(None,None))y0,y1=util.dimension_range(y0,y1,self.ylim,(None,None))ifnotself.drawn:x_range,y_range=((y0,y1),(x0,x1))ifself.invert_axeselse((x0,x1),(y0,y1))forstreamingetattr(self,'source_streams',[]):ifisinstance(stream,RangeX):params={'x_range':x_range}elifisinstance(stream,RangeY):params={'y_range':y_range}elifisinstance(stream,RangeXY):params={'x_range':x_range,'y_range':y_range}else:continuestream.update(**params)ifstreamnotinself._triggerand(self.xlimorself.ylim):self._trigger.append(stream)ifisinstance(self.projection,str)andself.projection=='3d':z0,z1=util.dimension_range(z0,z1,self.zlim,(None,None))return(x0,y0,z0,x1,y1,z1)return(x0,y0,x1,y1)
def_get_axis_labels(self,dimensions,xlabel=None,ylabel=None,zlabel=None):ifself.xlabelisnotNone:xlabel=self.xlabelelifdimensionsandxlabelisNone:xdims=dimensions[0]xlabel=dim_axis_label(xdims)ifxdimselse''ifself.ylabelisnotNone:ylabel=self.ylabeleliflen(dimensions)>=2andylabelisNone:ydims=dimensions[1]ylabel=dim_axis_label(ydims)ifydimselse''ifgetattr(self,'zlabel',None)isnotNone:zlabel=self.zlabelelif(isinstance(self.projection,str)andself.projection=='3d'andlen(dimensions)>=3andzlabelisNone):zlabel=dim_axis_label(dimensions[2])ifdimensions[2]else''returnxlabel,ylabel,zlabeldef_format_title_components(self,key,dimensions=True,separator='\n'):frame=self._get_frame(key)ifframeisNone:return('','','','')type_name=type(frame).__name__group=frame.groupifframe.group!=type_nameelse''label=frame.labelifself.layout_dimensionsordimensions:dim_title=self._frame_title(key,separator=separator)else:dim_title=''return(label,group,type_name,dim_title)def_parse_backend_opt(self,opt,plot,model_accessor_aliases):""" Parses a custom option of the form 'model.accessor.option' and returns the corresponding model and accessor. """accessors=opt.split('.')iflen(accessors)<2:self.param.warning(f"Custom option {opt!r} expects at least ""two accessors separated by '.'")returnmodel_accessor=accessors[0]# convert alias to handle key (figure -> fig)model_accessor=model_accessor_aliases.get(model_accessor)ormodel_accessorifmodel_accessorinself.handles:model=self.handles[model_accessor]elifhasattr(plot,model_accessor):model=getattr(plot,model_accessor)else:self.param.warning(f"{model_accessor} model could not be resolved "f"on {type(self).__name__!r} plot. "f"Ensure the {opt!r} custom option spec "f"references a valid model in the "f"plot.handles {list(self.handles.keys())!r} or on the underlying "f"figure object.")returnforaccinaccessors[1:-1]:# the logic handles resolving something like:# legend.get_texts()[0].set_fontsizeif'['inaccandacc.endswith(']'):getitem_index=acc.index('[')# gets the '0:2' or '0,2' or ':2' or '2:'getitem_spec=acc[getitem_index+1:-1]try:if':'ingetitem_spec:# slice notationslice_parts=getitem_spec.split(':')slice_start=Noneifslice_parts[0]==''elseint(slice_parts[0])slice_stop=Noneifslice_parts[1]==''elseint(slice_parts[1])slice_step=Noneiflen(slice_parts)<3orslice_parts[2]==''elseint(slice_parts[2])getitem_acc=slice(slice_start,slice_stop,slice_step)elif','ingetitem_spec:# multiple itemsgetitem_acc=[literal_eval(item.strip())foritemingetitem_spec.split(',')]else:# single indexgetitem_acc=literal_eval(getitem_spec)exceptException:self.param.warning(f"Could not evaluate getitem {getitem_spec!r} "f"in custom option spec {opt!r}.")model=Nonebreak# gets the 'legend.get_texts()'acc=acc[:getitem_index]else:getitem_acc=Noneif"("inaccand")"inacc:method_ini_index=acc.index("(")method_end_index=acc.index(")")method_spec=acc[method_ini_index+1:method_end_index]try:ifmethod_spec:method_parts=method_spec.split(',')method_args=[]method_kwargs={}forpartinmethod_parts:if'='inpart:# Handle keyword argumentkey,value=part.split('=')method_kwargs[key.strip()]=literal_eval(value.strip())else:# Handle regular argumentmethod_args.append(literal_eval(part.strip()))else:method_args=()method_kwargs={}exceptException:self.param.warning(f"Could not evaluate method arguments {method_spec!r} "f"in custom option spec {opt!r}.")model=Nonebreakacc=acc[:method_ini_index]# finally, we do something with all the things we gathered aboveifnotisinstance(model,list):model=getattr(model,acc)(*method_args,**method_kwargs)else:model=[getattr(m,acc)(*method_args,**method_kwargs)forminmodel]ifgetitem_accisnotNone:ifnotisinstance(getitem_acc,list):model=model.__getitem__(getitem_acc)else:model=[model.__getitem__(i)foriingetitem_acc]acc=acc[method_end_index:]ifacc==""ormodelisNone:continueifnothasattr(model,acc):self.param.warning(f"Could not resolve {acc!r} attribute on "f"{type(model).__name__!r} model. Ensure the "f"custom option spec you provided "f"references a valid submodel.")model=Nonebreakmodel=getattr(model,acc)attr_accessor=accessors[-1]returnmodel,attr_accessor
[docs]defupdate_frame(self,key,ranges=None):""" Set the plot(s) to the given frame number. Operates by manipulating the matplotlib objects held in the self._handles dictionary. If n is greater than the number of available frames, update using the last available frame. """
[docs]classGenericOverlayPlot(GenericElementPlot):""" Plotting baseclass to render (Nd)Overlay objects. It implements methods to handle the creation of ElementPlots, coordinating style groupings and zorder for all layers across a HoloMap. It also allows collapsing of layers via the Compositor. """batched=param.Boolean(default=True,doc=""" Whether to plot Elements NdOverlay in a batched plotting call if possible. Disables legends and zorder may not be preserved.""")legend_limit=param.Integer(default=25,doc=""" Number of rendered glyphs before legends are disabled.""")show_legend=param.Boolean(default=True,doc=""" Whether to show legend for the plot.""")style_grouping=param.Integer(default=2,doc=""" The length of the type.group.label spec that will be used to group Elements into style groups. A style_grouping value of 1 will group just by type, a value of 2 will group by type and group, and a value of 3 will group by the full specification.""")_passed_handles=[]# Options not to be propagated in multi_y mode to allow independent control of y-axes_multi_y_unpropagated=['yaxis','ylim','invert_yaxis','logy']def__init__(self,overlay,ranges=None,batched=True,keys=None,group_counter=None,**params):if'projection'notinparams:params['projection']=self._get_projection(overlay)super().__init__(overlay,ranges=ranges,keys=keys,batched=batched,**params)if('multi_y'inself.param)andself.multi_y:forsinself.streams:intersection=set(s.param)&{'y','y_selection','bounds','boundsy'}ifintersection:self.param.warning(f'{type(s).__name__} stream parameters'f' {list(intersection)} not yet supported with multi_y=True')# Apply data collapseself.hmap=self._apply_compositor(self.hmap,ranges,self.keys)self.map_lengths=Counter()self.group_counter=Counter()ifgroup_counterisNoneelsegroup_counterself.cyclic_index_lookup={}self.zoffset=0self.subplots=self._create_subplots(ranges)self.traverse(lambdax:setattr(x,'comm',self.comm))self.top_level=keysisNoneself.dynamic_subplots=[]ifself.top_level:self.traverse(lambdax:attach_streams(self,x.hmap,2),[GenericElementPlot])def_apply_compositor(self,holomap,ranges=None,keys=None,dimensions=None):""" Given a HoloMap compute the appropriate (mapwise or framewise) ranges in order to apply the Compositor collapse operations in display mode (data collapse should already have happened). """# Compute framewise normalizationdefaultdim=holomap.ndims==1andholomap.kdims[0].name!='Frame'ifkeysandrangesanddimensionsandnotdefaultdim:dim_inds=[dimensions.index(d)fordinholomap.kdims]sliced_keys=[tuple(k[i]foriindim_inds)forkinkeys]frame_ranges=dict([(slckey,self.compute_ranges(holomap,key,ranges[key]))forkey,slckeyinzip(keys,sliced_keys)ifslckeyinholomap.data.keys()])else:mapwise_ranges=self.compute_ranges(holomap,None,None)frame_ranges=dict([(key,self.compute_ranges(holomap,key,mapwise_ranges))forkeyinholomap.data.keys()])ranges=frame_ranges.values()withdisable_pipeline():collapsed=Compositor.collapse(holomap,(ranges,frame_ranges.keys()),mode='display')returncollapseddef_create_subplots(self,ranges):# Check if plot should be batchedordering=util.layer_sort(self.hmap)batched=self.batchedandtype(self.hmap.last)isNdOverlayifbatched:backend=self.renderer.backendbatchedplot=Store.registry[backend].get(self.hmap.last.type)if(batchedandbatchedplotand'batched'inbatchedplot._plot_methodsand(notself.show_legendorlen(ordering)>self.legend_limit)):self.batched=Truekeys,vmaps=[()],[self.hmap]else:self.batched=Falsekeys,vmaps=self.hmap._split_overlays()ifisinstance(self.hmap,DynamicMap):dmap_streams=[get_nested_streams(layer)forlayerinsplit_dmap_overlay(self.hmap)]else:dmap_streams=[None]*len(keys)# Compute global orderinglength=self.style_groupinggroup_fn=lambdax:(x.type.__name__,x.last.group,x.last.label)forminvmaps:self.map_lengths[group_fn(m)[:length]]+=1subplots={}for(key,vmap,streams)inzip(keys,vmaps,dmap_streams):subplot=self._create_subplot(key,vmap,streams,ranges)ifsubplotisNone:continueifnotisinstance(key,tuple):key=(key,)subplots[key]=subplotifisinstance(subplot,GenericOverlayPlot):self.zoffset+=len(subplot.subplots.keys())-1ifnotsubplots:raiseSkipRendering("%s backend could not plot any Elements ""in the Overlay."%self.renderer.backend)returnsubplotsdef_create_subplot(self,key,obj,streams,ranges):registry=Store.registry[self.renderer.backend]ordering=util.layer_sort(self.hmap)overlay_type=1ifself.hmap.type==Overlayelse2group_fn=lambdax:(x.type.__name__,x.last.group,x.last.label)opts={'overlaid':overlay_type}ifself.hmap.type==Overlay:style_key=(obj.type.__name__,)+keyifself.overlay_dims:opts['overlay_dims']=self.overlay_dimselse:ifnotisinstance(key,tuple):key=(key,)style_key=group_fn(obj)+keyopts['overlay_dims']=dict(zip(self.hmap.last.kdims,key))ifself.batched:vtype=type(obj.last.last)oidx=0else:vtype=type(obj.last)ifstyle_keynotinordering:ordering.append(style_key)oidx=ordering.index(style_key)plottype=registry.get(vtype,None)ifplottypeisNone:self.param.warning("No plotting class for {} type and {} backend ""found. ".format(vtype.__name__,self.renderer.backend))returnNone# Get zorder and style counterlength=self.style_groupinggroup_key=style_key[:length]zorder=self.zorder+oidx+self.zoffsetcyclic_index=self.group_counter[group_key]self.cyclic_index_lookup[style_key]=cyclic_indexself.group_counter[group_key]+=1group_length=self.map_lengths[group_key]ifnotisinstance(plottype,PlotSelector)andissubclass(plottype,GenericOverlayPlot):opts['group_counter']=self.group_counteropts['show_legend']=self.show_legendifnotany(len(frame)forframeinobj):self.param.warning('%s is empty and will be skipped ''during plotting'%obj.last)returnNoneelifself.batchedand'batched'inplottype._plot_methods:param_vals=self.param.values()propagate={opt:param_vals[opt]foroptinself._propagate_optionsifoptinparam_vals}opts['batched']=self.batchedopts['overlaid']=self.overlaidopts.update(propagate)iflen(ordering)>self.legend_limit:opts['show_legend']=Falsestyle=self.lookup_options(obj.last,'style').max_cycles(group_length)passed_handles={k:vfork,vinself.handles.items()ifkinself._passed_handles}plotopts=dict(opts,cyclic_index=cyclic_index,invert_axes=self.invert_axes,dimensions=self.dimensions,keys=self.keys,layout_dimensions=self.layout_dimensions,ranges=ranges,show_title=self.show_title,style=style,uniform=self.uniform,fontsize=self.fontsize,streams=streams,renderer=self.renderer,adjoined=self.adjoined,stream_sources=self.stream_sources,projection=self.projection,fontscale=self.fontscale,zorder=zorder,root=self.root,**passed_handles)returnplottype(obj,**plotopts)def_match_subplot(self,key,subplot,items,element):found=Falsetemp_items=list(items)whilenotfound:idx,spec,exact=dynamic_update(self,subplot,key,element,temp_items)ifidxisnotNone:ifnotexact:exact_matches=[dynamic_update(self,subplot,k,element,temp_items)forkinself.subplots]exact_matches=[mforminexact_matchesifm[-1]]ifexact_matches:idx=exact_matches[0][0]_,el=temp_items.pop(idx)continuefound=TrueifidxisnotNone:idx=items.index(temp_items.pop(idx))returnidx,spec,exactdef_create_dynamic_subplots(self,key,items,ranges,**init_kwargs):""" Handles the creation of new subplots when a DynamicMap returns a changing set of elements in an Overlay. """length=self.style_groupinggroup_fn=lambdax:(x.type.__name__,x.last.group,x.last.label)fork,objinitems:vmap=self.hmap.clone([(key,obj)])self.map_lengths[group_fn(vmap)[:length]]+=1subplot=self._create_subplot(k,vmap,[],ranges)ifsubplotisNone:continuesubplot.document=self.documentifself.comm:subplot.comm=self.commself.subplots[k]=subplotsubplot.initialize_plot(ranges,**init_kwargs)subplot.update_frame(key,ranges,element=obj)self.dynamic_subplots.append(subplot)def_update_subplot(self,subplot,spec):""" Updates existing subplots when the subplot has been assigned to plot an element that is not an exact match to the object it was initially assigned. """# See if the precise spec has already been assigned a cyclic# index otherwise generate a new oneifspecinself.cyclic_index_lookup:cyclic_index=self.cyclic_index_lookup[spec]else:group_key=spec[:self.style_grouping]self.group_counter[group_key]+=1cyclic_index=self.group_counter[group_key]self.cyclic_index_lookup[spec]=cyclic_indexsubplot.cyclic_index=cyclic_indexifsubplot.overlay_dims:odim_key=util.wrap_tuple(spec[-1])new_dims=zip(subplot.overlay_dims,odim_key)subplot.overlay_dims=dict(new_dims)def_get_subplot_extents(self,overlay,ranges,range_type,dimension=None):""" Iterates over all subplots and collects the extents of each. """ifrange_type=='combined':extents={'extents':[],'soft':[],'hard':[],'data':[]}else:extents={range_type:[]}items=overlay.items()ifself.batchedandself.subplots:subplot=next(iter(self.subplots.values()))subplots=[(k,subplot)forkinoverlay.data.keys()]else:subplots=self.subplots.items()forkey,subplotinsubplots:found=FalseifsubplotisNone:continuelayer=overlay.data.get(key,None)ifisinstance(self.hmap,DynamicMap)andlayerisNone:for_,layerinitems:ifisinstance(layer,subplot.hmap.type):found=Truebreakifnotfound:layer=NoneiflayerisNoneornotsubplot.apply_ranges:continueifdimensionandnotsubplot._has_axis_dimension(layer,dimension):continueifisinstance(layer,CompositeOverlay):sp_ranges=rangeselse:sp_ranges=util.match_spec(layer,ranges)ifrangeselse{}forrtinextents:extent=subplot.get_extents(layer,sp_ranges,range_type=rt)extents[rt].append(extent)returnextents
[docs]classGenericCompositePlot(DimensionedPlot):def__init__(self,layout,keys=None,dimensions=None,**params):if'uniform'notinparams:params['uniform']=traversal.uniform(layout)self.top_level=keysisNoneifself.top_level:dimensions,keys=traversal.unique_dimkeys(layout)dynamic,unbounded=get_dynamic_mode(layout)ifunbounded:initialize_unbounded(layout,dimensions,keys[0])self.layout=layoutsuper().__init__(keys=keys,dynamic=dynamic,dimensions=dimensions,**params)nested_streams=layout.traverse(lambdax:get_nested_streams(x),[DynamicMap])self.streams=list({sforstreamsinnested_streamsforsinstreams})self._link_dimensioned_streams()def_link_dimensioned_streams(self):""" Should perform any linking required to update titles when dimensioned streams change. """def_get_frame(self,key):""" Creates a clone of the Layout with the nth-frame for each Element. """cached=self.current_keyisNonelayout_frame=self.layout.clone(shared_data=False)ifkey==self.current_keyandnotself._force:returnself.current_frameelse:self.current_key=keykey_map=dict(zip([d.namefordinself.dimensions],key))forpath,iteminself.layout.items():frame=get_nested_plot_frame(item,key_map,cached)ifframeisnotNone:layout_frame[path]=frametraverse_setter(self,'_force',False)self.current_frame=layout_framereturnlayout_framedef_format_title_components(self,key,dimensions=True,separator='\n'):dim_title=self._frame_title(key,3,separator)ifdimensionselse''layout=self.layouttype_name=type(self.layout).__name__group=util.bytes_to_unicode(layout.groupiflayout.group!=type_nameelse'')label=util.bytes_to_unicode(layout.label)return(label,group,type_name,dim_title)
[docs]classGenericLayoutPlot(GenericCompositePlot):""" A GenericLayoutPlot accepts either a Layout or a NdLayout and displays the elements in a cartesian grid in scanline order. """transpose=param.Boolean(default=False,doc=""" Whether to transpose the layout when plotting. Switches from row-based left-to-right and top-to-bottom scanline order to column-based top-to-bottom and left-to-right order.""")def__init__(self,layout,**params):ifnotisinstance(layout,(NdLayout,Layout)):raiseValueError("GenericLayoutPlot only accepts Layout objects.")iflen(layout.values())==0:raiseSkipRendering(warn=False)super().__init__(layout,**params)self.subplots={}self.rows,self.cols=layout.shape[::-1]ifself.transposeelselayout.shapeself.coords=list(product(range(self.rows),range(self.cols)))
[docs]classGenericAdjointLayoutPlot(Plot):""" AdjointLayoutPlot allows placing up to three Views in a number of predefined and fixed layouts, which are defined by the layout_dict class attribute. This allows placing subviews next to a main plot in either a 'top' or 'right' position. """layout_dict={'Single':{'positions':['main']},'Dual':{'positions':['main','right']},'Triple':{'positions':['main','right','top']}}