holoviz / holoviews

With Holoviews, your data visualizes itself.
https://holoviews.org
BSD 3-Clause "New" or "Revised" License
2.69k stars 402 forks source link

Feature request radial plots #2527

Open ea42gh opened 6 years ago

ea42gh commented 6 years ago

Given h=hv.Curve((angle,r)).options(backend='matplotlib',projection='radial') it might be useful to have an option specifying that angles are given in degrees:

I'd like to plot the same Curve with bokeh and matplotlib and actually use degrees rather than radians for the angles

ea42gh commented 6 years ago

Major feature if you want to add radial plots in both bokeh and matplotlib; what I had in mind was simpler: bokeh plot with (angle,r) treated as cartesian coordinates, and a corresponding matplotlib plot with (angle,r) treated as polar coordinates on a radial plot.

Use case: a beamformer pattern

ea42gh commented 6 years ago

beampatterns

The only reason the two plots need different hv.Curve() definitions are the angles: the bokeh version takes degrees, while the matplotlib version needs radians.

Incidentally, I can't get hv.HLine to work in the matplotlib plot

ea42gh commented 6 years ago

A much simplified version with most of the physics removed is

phi     = np.linspace(-90, 90, 181)
phi_rad = np.pi/180 * phi

def AF(n, phi_rad):
    '''array factor for n elements'''
    psi2  = 0.5*phi_rad
    with np.errstate(divide='ignore', invalid='ignore'):
        f_phi  = np.sin( n*psi2 ) / (float(n) * np.sin(psi2))
    f_phi[np.isnan( f_phi )] =  1.

    return 10.*np.log10( np.abs(f_phi) )

def plot_AF( n_elems ):
    x_dim = hv.Dimension('x', label='ψ',            unit = 'degree')
    y_dim = hv.Dimension('y', label='Array Factor', unit = 'dB')

    o_bokeh = {'Curve' : { 'show_grid':True,'xticks':8}}
    o_mpl   = {'Curve' : { 'projection':'polar', 'show_grid':True,'xticks':4}}

    h_xy = hv.Curve( (phi, AF( n_elems, phi_rad)),x_dim,y_dim ) \
             .redim.range(y=(-25,1))

    h_rt = hv.Curve( (phi_rad, AF( n_elems, phi_rad)),x_dim, y_dim ) \
             .redim.range(y=(-25,1))

    h_rt = hv.Div( hv.renderer('matplotlib').html(h_rt.options(o_mpl, backend='matplotlib')) )
    return (h_xy.options(o_bokeh, backend='bokeh')+h_rt).relabel('Array Factor Plot')

hv.DynamicMap( plot_AF, kdims=['n_elems'] ) \
  .redim.values(n_elems=range(1,101))

afplot0

ea42gh commented 6 years ago

Or, putting in more of the physics and hiding the global variables

def gen_AFplots():
    phi       = np.linspace(-90, 90, 181)
    phi_rad   = np.pi/180 * phi

    def AF(n, d_wl, steering_angle_rad ):
        '''array factor for n elements, d_wl = element_spacing/wavelength'''
        ss_phi = np.sin( phi_rad )-np.sin(steering_angle_rad )
        psi2   = np.pi*d_wl*ss_phi
        with np.errstate(divide='ignore', invalid='ignore'):
            f_phi  = np.sin( n*psi2 ) / (float(n) * np.sin(psi2))
        f_phi[np.isnan( f_phi )] =  1.

        return 10.*np.log10( np.abs(f_phi) 
                           )
    def plot( n_elems, d_wl, steering_angle ):
        x_dim = hv.Dimension('x', label='φ',            unit = 'degree')
        y_dim = hv.Dimension('y', label='Array Factor', unit = 'dB')

        ticks = list( np.pi/9*np.arange(-8, 9, 1) )

        o_bokeh = {'Curve' : { 'show_grid':True,'xticks':8}, 'VLine' : {'line_width':.5, 'color':'red'}}
        o_mpl   = {'Curve' : { 'projection':'polar', 'show_grid':True,'xticks':ticks}, 'VLine' : {'linewidth':1, 'color':'red'}}

        af   = AF( n_elems, d_wl, np.pi/180*steering_angle)

        h_xy = hv.Curve( (phi, af),x_dim,y_dim ) \
                 .redim.range(y=(-25,1))*\
               hv.VLine( steering_angle)

        h_rt = hv.Curve( (phi_rad, af),x_dim, y_dim ) \
                 .redim.range(y=(-25,1))*\
               hv.VLine( np.pi/180*steering_angle)

        h_rt = hv.Div( hv.renderer('matplotlib').html(h_rt.options(o_mpl, backend='matplotlib')) )
        return h_xy.options(o_bokeh, backend='bokeh')+h_rt
    return plot

hv.DynamicMap( gen_AFplots(), kdims=['n_elems', 'd_wl', 'steering_angle'] ) \
  .redim.values(n_elems=range(1,101)).redim.range( d_wl=(1e-1,.5), steering_angle=(-90,90))

where d_wl is the ratio of the element separation to the wavelength of the incoming signal, and phi_0 is the steering angle.

The array factor $AF$ for a uniform linear array is given by

$$ AF(\psi) = \frac{\sin (\tfrac{1}{2} N \psi) }{ N \sin( \tfrac{1}{2} \psi)} $$ where

Do you want me to put this in a notebook with some text?

jlstevens commented 6 years ago

Thanks for that! If it isn't too much trouble, it would be nice to see screenshots/gifs of the two versions. Of course I can run the code myself, but it is useful to show something everyone can compare...

ea42gh commented 6 years ago

afplot

ea42gh commented 5 years ago

In hv version 1.11.0 this now fails:

running it a first time yields a display with inoperable controls rerunning it a second time fails outright with

D:\PYTHON\lib\site-packages\holoviews\plotting\mpl\plot.py in _compute_gridspec(self, layout)
    954                                                  layout_dimensions, frame_ranges,
    955                                                  dict(zip(positions, subaxes)),
--> 956                                                  num=0 if empty else layout_count)
    957             subplots, adjoint_layout, _ = subplot_data
    958             layout_axes[(r, c)] = subaxes

D:\PYTHON\lib\site-packages\holoviews\plotting\mpl\plot.py in _create_subplots(self, layout, positions, layout_dimensions, ranges, axes, num, create)
   1067             if isinstance(view, GridSpace):
   1068                 plotopts['create_axes'] = ax is not None
-> 1069             plot_type = Store.registry['matplotlib'][vtype]
   1070             if pos != 'main' and vtype in MPLPlot.sideplots:
   1071                 plot_type = MPLPlot.sideplots[vtype]

KeyError: <class 'holoviews.element.annotation.Div'>
sverley commented 1 year ago

This is an important feature for datascientist